@kensio/colophon 2.11.1 → 2.13.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 (198) hide show
  1. package/README.md +4 -1
  2. package/dist/astro/index.d.ts.map +1 -1
  3. package/dist/astro/index.js +0 -1
  4. package/dist/astro/index.js.map +1 -1
  5. package/dist/config/defaults.d.ts +24 -0
  6. package/dist/config/defaults.d.ts.map +1 -1
  7. package/dist/config/defaults.js +29 -0
  8. package/dist/config/defaults.js.map +1 -1
  9. package/dist/config/index.d.ts.map +1 -1
  10. package/dist/config/index.js +2 -1
  11. package/dist/config/index.js.map +1 -1
  12. package/dist/config/size.d.ts.map +1 -1
  13. package/dist/config/size.js +3 -0
  14. package/dist/config/size.js.map +1 -1
  15. package/dist/layout/fill.d.ts +32 -0
  16. package/dist/layout/fill.d.ts.map +1 -0
  17. package/dist/layout/fill.js +34 -0
  18. package/dist/layout/fill.js.map +1 -0
  19. package/dist/layout/index.d.ts +4 -2
  20. package/dist/layout/index.d.ts.map +1 -1
  21. package/dist/layout/index.js +2 -1
  22. package/dist/layout/index.js.map +1 -1
  23. package/dist/measure/faces.d.ts.map +1 -1
  24. package/dist/measure/faces.js.map +1 -1
  25. package/dist/render/svg.js +1 -1
  26. package/dist/render/svg.js.map +1 -1
  27. package/dist/templates/banner/index.js +2 -2
  28. package/dist/templates/banner/index.js.map +1 -1
  29. package/dist/templates/card/index.js +2 -2
  30. package/dist/templates/card/index.js.map +1 -1
  31. package/dist/templates/code/bar.d.ts +24 -0
  32. package/dist/templates/code/bar.d.ts.map +1 -0
  33. package/dist/templates/code/bar.js +31 -0
  34. package/dist/templates/code/bar.js.map +1 -0
  35. package/dist/templates/code/gutter.d.ts +30 -0
  36. package/dist/templates/code/gutter.d.ts.map +1 -0
  37. package/dist/templates/code/gutter.js +64 -0
  38. package/dist/templates/code/gutter.js.map +1 -0
  39. package/dist/templates/code/index.d.ts.map +1 -1
  40. package/dist/templates/code/index.js +34 -15
  41. package/dist/templates/code/index.js.map +1 -1
  42. package/dist/templates/code/layout.d.ts +6 -1
  43. package/dist/templates/code/layout.d.ts.map +1 -1
  44. package/dist/templates/code/layout.js +8 -3
  45. package/dist/templates/code/layout.js.map +1 -1
  46. package/dist/templates/code/mark/draw.d.ts +26 -0
  47. package/dist/templates/code/mark/draw.d.ts.map +1 -0
  48. package/dist/templates/code/mark/draw.js +41 -0
  49. package/dist/templates/code/mark/draw.js.map +1 -0
  50. package/dist/templates/code/mark/index.d.ts +16 -0
  51. package/dist/templates/code/mark/index.d.ts.map +1 -0
  52. package/dist/templates/code/mark/index.js +34 -0
  53. package/dist/templates/code/mark/index.js.map +1 -0
  54. package/dist/templates/code/mark/locate.d.ts +20 -0
  55. package/dist/templates/code/mark/locate.d.ts.map +1 -0
  56. package/dist/templates/code/mark/locate.js +56 -0
  57. package/dist/templates/code/mark/locate.js.map +1 -0
  58. package/dist/templates/code/mark/read.d.ts +30 -0
  59. package/dist/templates/code/mark/read.d.ts.map +1 -0
  60. package/dist/templates/code/mark/read.js +61 -0
  61. package/dist/templates/code/mark/read.js.map +1 -0
  62. package/dist/templates/code/plate.d.ts +7 -1
  63. package/dist/templates/code/plate.d.ts.map +1 -1
  64. package/dist/templates/code/plate.js +14 -4
  65. package/dist/templates/code/plate.js.map +1 -1
  66. package/dist/templates/footer.d.ts +9 -1
  67. package/dist/templates/footer.d.ts.map +1 -1
  68. package/dist/templates/footer.js +24 -0
  69. package/dist/templates/footer.js.map +1 -1
  70. package/dist/templates/frame.d.ts.map +1 -1
  71. package/dist/templates/frame.js +5 -7
  72. package/dist/templates/frame.js.map +1 -1
  73. package/dist/templates/index.d.ts +1 -0
  74. package/dist/templates/index.d.ts.map +1 -1
  75. package/dist/templates/index.js +3 -0
  76. package/dist/templates/index.js.map +1 -1
  77. package/dist/templates/terminal/index.js +4 -4
  78. package/dist/templates/terminal/index.js.map +1 -1
  79. package/dist/templates/thumbnail/index.d.ts +21 -0
  80. package/dist/templates/thumbnail/index.d.ts.map +1 -0
  81. package/dist/templates/thumbnail/index.js +68 -0
  82. package/dist/templates/thumbnail/index.js.map +1 -0
  83. package/dist/templates/thumbnail/lines.d.ts +38 -0
  84. package/dist/templates/thumbnail/lines.d.ts.map +1 -0
  85. package/dist/templates/thumbnail/lines.js +76 -0
  86. package/dist/templates/thumbnail/lines.js.map +1 -0
  87. package/dist/templates/window/dots.d.ts +11 -0
  88. package/dist/templates/window/dots.d.ts.map +1 -0
  89. package/dist/templates/window/dots.js +32 -0
  90. package/dist/templates/window/dots.js.map +1 -0
  91. package/dist/templates/{terminal/window.d.ts → window/index.d.ts} +4 -2
  92. package/dist/templates/window/index.d.ts.map +1 -0
  93. package/dist/templates/window/index.js +48 -0
  94. package/dist/templates/window/index.js.map +1 -0
  95. package/dist/templates/window/title.d.ts +9 -0
  96. package/dist/templates/window/title.d.ts.map +1 -0
  97. package/dist/templates/window/title.js +24 -0
  98. package/dist/templates/window/title.js.map +1 -0
  99. package/dist/text/fill.d.ts +34 -0
  100. package/dist/text/fill.d.ts.map +1 -0
  101. package/dist/text/fill.js +57 -0
  102. package/dist/text/fill.js.map +1 -0
  103. package/dist/text/fit.d.ts.map +1 -1
  104. package/dist/text/fit.js +2 -6
  105. package/dist/text/fit.js.map +1 -1
  106. package/dist/text/index.d.ts +2 -0
  107. package/dist/text/index.d.ts.map +1 -1
  108. package/dist/text/index.js +1 -0
  109. package/dist/text/index.js.map +1 -1
  110. package/dist/text/step.d.ts +13 -0
  111. package/dist/text/step.d.ts.map +1 -0
  112. package/dist/text/step.js +17 -0
  113. package/dist/text/step.js.map +1 -0
  114. package/dist/text/unbroken.d.ts +22 -0
  115. package/dist/text/unbroken.d.ts.map +1 -0
  116. package/dist/text/unbroken.js +29 -0
  117. package/dist/text/unbroken.js.map +1 -0
  118. package/dist/texture/chevrons.d.ts +13 -0
  119. package/dist/texture/chevrons.d.ts.map +1 -0
  120. package/dist/texture/chevrons.js +22 -0
  121. package/dist/texture/chevrons.js.map +1 -0
  122. package/dist/texture/grid.d.ts +14 -0
  123. package/dist/texture/grid.d.ts.map +1 -0
  124. package/dist/texture/grid.js +38 -0
  125. package/dist/texture/grid.js.map +1 -0
  126. package/dist/texture/halftone.d.ts +14 -0
  127. package/dist/texture/halftone.d.ts.map +1 -0
  128. package/dist/texture/halftone.js +55 -0
  129. package/dist/texture/halftone.js.map +1 -0
  130. package/dist/texture/honeycomb.d.ts +14 -0
  131. package/dist/texture/honeycomb.d.ts.map +1 -0
  132. package/dist/texture/honeycomb.js +43 -0
  133. package/dist/texture/honeycomb.js.map +1 -0
  134. package/dist/texture/index.d.ts +15 -4
  135. package/dist/texture/index.d.ts.map +1 -1
  136. package/dist/texture/index.js +64 -14
  137. package/dist/texture/index.js.map +1 -1
  138. package/dist/texture/moire.d.ts +18 -0
  139. package/dist/texture/moire.d.ts.map +1 -0
  140. package/dist/texture/moire.js +44 -0
  141. package/dist/texture/moire.js.map +1 -0
  142. package/dist/texture/pattern.d.ts +10 -0
  143. package/dist/texture/pattern.d.ts.map +1 -1
  144. package/dist/texture/pattern.js +36 -13
  145. package/dist/texture/pattern.js.map +1 -1
  146. package/dist/texture/rays.d.ts +13 -0
  147. package/dist/texture/rays.d.ts.map +1 -0
  148. package/dist/texture/rays.js +36 -0
  149. package/dist/texture/rays.js.map +1 -0
  150. package/dist/texture/resolve.d.ts +10 -0
  151. package/dist/texture/resolve.d.ts.map +1 -1
  152. package/dist/texture/resolve.js +15 -0
  153. package/dist/texture/resolve.js.map +1 -1
  154. package/dist/texture/scallops.d.ts +14 -0
  155. package/dist/texture/scallops.d.ts.map +1 -0
  156. package/dist/texture/scallops.js +29 -0
  157. package/dist/texture/scallops.js.map +1 -0
  158. package/dist/texture/tile.d.ts +14 -0
  159. package/dist/texture/tile.d.ts.map +1 -0
  160. package/dist/texture/tile.js +23 -0
  161. package/dist/texture/tile.js.map +1 -0
  162. package/dist/texture/topographic/contours.d.ts +13 -0
  163. package/dist/texture/topographic/contours.d.ts.map +1 -0
  164. package/dist/texture/topographic/contours.js +72 -0
  165. package/dist/texture/topographic/contours.js.map +1 -0
  166. package/dist/texture/topographic/field.d.ts +8 -0
  167. package/dist/texture/topographic/field.d.ts.map +1 -0
  168. package/dist/texture/topographic/field.js +29 -0
  169. package/dist/texture/topographic/field.js.map +1 -0
  170. package/dist/texture/topographic/index.d.ts +17 -0
  171. package/dist/texture/topographic/index.d.ts.map +1 -0
  172. package/dist/texture/topographic/index.js +48 -0
  173. package/dist/texture/topographic/index.js.map +1 -0
  174. package/dist/theme/presets.d.ts +13 -5
  175. package/dist/theme/presets.d.ts.map +1 -1
  176. package/dist/theme/presets.js +16 -5
  177. package/dist/theme/presets.js.map +1 -1
  178. package/dist/types.d.ts +259 -3
  179. package/dist/types.d.ts.map +1 -1
  180. package/dist/validate/collect.d.ts.map +1 -1
  181. package/dist/validate/collect.js +2 -1
  182. package/dist/validate/collect.js.map +1 -1
  183. package/dist/validate/keys.d.ts +37 -0
  184. package/dist/validate/keys.d.ts.map +1 -1
  185. package/dist/validate/keys.js +132 -2
  186. package/dist/validate/keys.js.map +1 -1
  187. package/dist/validate/texture.d.ts +4 -5
  188. package/dist/validate/texture.d.ts.map +1 -1
  189. package/dist/validate/texture.js +11 -20
  190. package/dist/validate/texture.js.map +1 -1
  191. package/dist/validate/values.d.ts +6 -0
  192. package/dist/validate/values.d.ts.map +1 -1
  193. package/dist/validate/values.js +16 -1
  194. package/dist/validate/values.js.map +1 -1
  195. package/package.json +3 -10
  196. package/dist/templates/terminal/window.d.ts.map +0 -1
  197. package/dist/templates/terminal/window.js +0 -84
  198. package/dist/templates/terminal/window.js.map +0 -1
@@ -0,0 +1,57 @@
1
+ import { nextSize } from "./step.js";
2
+ import { largestUnbroken } from "./unbroken.js";
3
+ import { wrapText } from "./wrap.js";
4
+ /**
5
+ * How many whole lines fit in the box at a given size, and never fewer than one.
6
+ *
7
+ * A box too short for a single line gets that line anyway, running over rather
8
+ * than being left empty. That is the rule `distribute` follows for a group that
9
+ * overflows: a template which has asked for the impossible wants to see it.
10
+ */
11
+ function linesInBox(maxHeight, advance) {
12
+ return Math.max(1, Math.floor(maxHeight / advance));
13
+ }
14
+ /**
15
+ * The size the search starts from, which is the largest one worth trying: no
16
+ * taller than a single line of the box, no larger than the caller's ceiling,
17
+ * and small enough that no word has to be broken to fit the width.
18
+ */
19
+ function startingSize(text, measure, options) {
20
+ const ceiling = Math.min(options.maxFontSize, Math.floor(options.maxHeight / options.lineHeight));
21
+ return Math.max(options.minFontSize, largestUnbroken(text, measure, options.maxWidth, ceiling));
22
+ }
23
+ /**
24
+ * Wrap text into a box, at the largest size the whole of it fits at.
25
+ *
26
+ * This is {@link fitText} with the question turned round. `fitText` is given a
27
+ * size and shrinks only when the words need more lines than the layout allows,
28
+ * so a short title is drawn at whatever size the template named and no larger.
29
+ * That is right where the text sits in a group with other things, since a
30
+ * heading that grew to fill the space would stop looking like a heading.
31
+ *
32
+ * It is wrong where the text is the picture. A YouTube thumbnail is looked at
33
+ * in a fraction of the space it was rendered at, so its words have to take the
34
+ * room they are given rather than sit in the middle of it, and how much room
35
+ * that is depends on how many words there are: three of them should be drawn
36
+ * much larger than twelve. So the search comes down from the largest size the
37
+ * box could hold until the wrapped block fits both the width and the height.
38
+ *
39
+ * Text that will not fit even at the floor is cut to the lines there is room
40
+ * for, which is what `fitText` does at its own floor and for the same reason:
41
+ * below a certain size the image has stopped saying anything anybody can read.
42
+ */
43
+ export function fillText(text, measure, options) {
44
+ const { maxWidth, maxHeight, lineHeight, minFontSize } = options;
45
+ let fontSize = startingSize(text, measure, options);
46
+ let lines = wrapText(text, maxWidth, (line) => measure(line, fontSize));
47
+ while (lines.length * fontSize * lineHeight > maxHeight &&
48
+ fontSize > minFontSize) {
49
+ fontSize = nextSize(fontSize, minFontSize);
50
+ lines = wrapText(text, maxWidth, (line) => measure(line, fontSize));
51
+ }
52
+ return {
53
+ lines: lines.slice(0, linesInBox(maxHeight, fontSize * lineHeight)),
54
+ fontSize,
55
+ };
56
+ }
57
+ //# sourceMappingURL=fill.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fill.js","sourceRoot":"","sources":["../../src/text/fill.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAcrC;;;;;;GAMG;AACH,SAAS,UAAU,CAAC,SAAiB,EAAE,OAAe;IACpD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,OAAO,CAAC,CAAC,CAAC;AACtD,CAAC;AAED;;;;GAIG;AACH,SAAS,YAAY,CACnB,IAAY,EACZ,OAAkB,EAClB,OAAoB;IAEpB,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CACtB,OAAO,CAAC,WAAW,EACnB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CACnD,CAAC;IAEF,OAAO,IAAI,CAAC,GAAG,CACb,OAAO,CAAC,WAAW,EACnB,eAAe,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,CAC1D,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,QAAQ,CACtB,IAAY,EACZ,OAAkB,EAClB,OAAoB;IAEpB,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC;IACjE,IAAI,QAAQ,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IACpD,IAAI,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IAExE,OACE,KAAK,CAAC,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS;QAChD,QAAQ,GAAG,WAAW,EACtB,CAAC;QACD,QAAQ,GAAG,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QAC3C,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IACtE,CAAC;IAED,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,SAAS,EAAE,QAAQ,GAAG,UAAU,CAAC,CAAC;QACnE,QAAQ;KACT,CAAC;AACJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"fit.d.ts","sourceRoot":"","sources":["../../src/text/fit.ts"],"names":[],"mappings":"AAEA,uEAAuE;AACvE,MAAM,MAAM,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;AAEnE,uEAAuE;AACvE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,oDAAoD;IACpD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,6EAA6E;IAC7E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,6DAA6D;AAC7D,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAKD;;;;;;;;;;;GAWG;AACH,wBAAgB,OAAO,CACrB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,SAAS,EAClB,OAAO,EAAE,UAAU,GAClB,UAAU,CAiBZ"}
1
+ {"version":3,"file":"fit.d.ts","sourceRoot":"","sources":["../../src/text/fit.ts"],"names":[],"mappings":"AAGA,uEAAuE;AACvE,MAAM,MAAM,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;AAEnE,uEAAuE;AACvE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,oDAAoD;IACpD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,6EAA6E;IAC7E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,6DAA6D;AAC7D,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,OAAO,CACrB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,SAAS,EAClB,OAAO,EAAE,UAAU,GAClB,UAAU,CAWZ"}
package/dist/text/fit.js CHANGED
@@ -1,6 +1,5 @@
1
+ import { nextSize } from "./step.js";
1
2
  import { wrapText } from "./wrap.js";
2
- /** How much of the size is given up each time the text does not fit. */
3
- const shrinkStep = 0.94;
4
3
  /**
5
4
  * Wrap text into the space it has, shrinking it until it fits.
6
5
  *
@@ -18,10 +17,7 @@ export function fitText(text, measure, options) {
18
17
  let fontSize = options.fontSize;
19
18
  let lines = wrapText(text, maxWidth, (line) => measure(line, fontSize));
20
19
  while (lines.length > maxLines && fontSize > minFontSize) {
21
- // Both terms are here to guarantee progress: the proportional step is what
22
- // makes the search short, and the whole pixel is what stops it stalling
23
- // once the sizes are small enough for rounding to swallow the step.
24
- fontSize = Math.max(minFontSize, Math.min(fontSize - 1, Math.floor(fontSize * shrinkStep)));
20
+ fontSize = nextSize(fontSize, minFontSize);
25
21
  lines = wrapText(text, maxWidth, (line) => measure(line, fontSize));
26
22
  }
27
23
  return { lines: lines.slice(0, maxLines), fontSize };
@@ -1 +1 @@
1
- {"version":3,"file":"fit.js","sourceRoot":"","sources":["../../src/text/fit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAqBrC,wEAAwE;AACxE,MAAM,UAAU,GAAG,IAAI,CAAC;AAExB;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,OAAO,CACrB,IAAY,EACZ,OAAkB,EAClB,OAAmB;IAEnB,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC;IACpD,IAAI,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAChC,IAAI,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IAExE,OAAO,KAAK,CAAC,MAAM,GAAG,QAAQ,IAAI,QAAQ,GAAG,WAAW,EAAE,CAAC;QACzD,2EAA2E;QAC3E,wEAAwE;QACxE,oEAAoE;QACpE,QAAQ,GAAG,IAAI,CAAC,GAAG,CACjB,WAAW,EACX,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,UAAU,CAAC,CAAC,CAC1D,CAAC;QACF,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IACtE,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC;AACvD,CAAC"}
1
+ {"version":3,"file":"fit.js","sourceRoot":"","sources":["../../src/text/fit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAqBrC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,OAAO,CACrB,IAAY,EACZ,OAAkB,EAClB,OAAmB;IAEnB,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC;IACpD,IAAI,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAChC,IAAI,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IAExE,OAAO,KAAK,CAAC,MAAM,GAAG,QAAQ,IAAI,QAAQ,GAAG,WAAW,EAAE,CAAC;QACzD,QAAQ,GAAG,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QAC3C,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IACtE,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC;AACvD,CAAC"}
@@ -4,6 +4,8 @@ export type { TextAttributes } from "./element.js";
4
4
  export { breakWord } from "./break.js";
5
5
  export { clampLine } from "./clamp.js";
6
6
  export { ellipsis } from "./ellipsis.js";
7
+ export { fillText } from "./fill.js";
8
+ export type { FillOptions } from "./fill.js";
7
9
  export { fitText } from "./fit.js";
8
10
  export type { FitOptions, FittedText, MeasureAt } from "./fit.js";
9
11
  export { wrapText } from "./wrap.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/text/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACnC,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/text/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACnC,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC"}
@@ -3,6 +3,7 @@ export { textElement } from "./element.js";
3
3
  export { breakWord } from "./break.js";
4
4
  export { clampLine } from "./clamp.js";
5
5
  export { ellipsis } from "./ellipsis.js";
6
+ export { fillText } from "./fill.js";
6
7
  export { fitText } from "./fit.js";
7
8
  export { wrapText } from "./wrap.js";
8
9
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/text/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAEnC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/text/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAEnC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The next size down to try, for a search that is shrinking text into a space.
3
+ *
4
+ * Both terms are here to guarantee progress: the proportional step is what
5
+ * makes the search short, and the whole pixel is what stops it stalling once
6
+ * the sizes are small enough for rounding to swallow the step.
7
+ *
8
+ * It is shared by the two searches rather than written out in each, since a
9
+ * step that differed between them would mean two layouts settling on different
10
+ * sizes for the same words with no reason a reader could name.
11
+ */
12
+ export declare function nextSize(fontSize: number, minFontSize: number): number;
13
+ //# sourceMappingURL=step.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"step.d.ts","sourceRoot":"","sources":["../../src/text/step.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAKtE"}
@@ -0,0 +1,17 @@
1
+ /** How much of the size is given up each time the text does not fit. */
2
+ const shrinkRatio = 0.94;
3
+ /**
4
+ * The next size down to try, for a search that is shrinking text into a space.
5
+ *
6
+ * Both terms are here to guarantee progress: the proportional step is what
7
+ * makes the search short, and the whole pixel is what stops it stalling once
8
+ * the sizes are small enough for rounding to swallow the step.
9
+ *
10
+ * It is shared by the two searches rather than written out in each, since a
11
+ * step that differed between them would mean two layouts settling on different
12
+ * sizes for the same words with no reason a reader could name.
13
+ */
14
+ export function nextSize(fontSize, minFontSize) {
15
+ return Math.max(minFontSize, Math.min(fontSize - 1, Math.floor(fontSize * shrinkRatio)));
16
+ }
17
+ //# sourceMappingURL=step.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"step.js","sourceRoot":"","sources":["../../src/text/step.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,MAAM,WAAW,GAAG,IAAI,CAAC;AAEzB;;;;;;;;;;GAUG;AACH,MAAM,UAAU,QAAQ,CAAC,QAAgB,EAAE,WAAmB;IAC5D,OAAO,IAAI,CAAC,GAAG,CACb,WAAW,EACX,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,WAAW,CAAC,CAAC,CAC3D,CAAC;AACJ,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { MeasureAt } from "./fit.js";
2
+ /**
3
+ * The largest size, no more than `ceiling`, at which every word fits on a line
4
+ * of its own.
5
+ *
6
+ * Text that is being grown to fill a box is the one case where wrapping is
7
+ * under pressure to break a word in half. `wrapText` breaks a word too wide for
8
+ * its line, which is the right answer when the size is settled and the
9
+ * alternative is a line running off the image, and the wrong one here: two
10
+ * lines of a broken word are shorter than one whole one, so a search measuring
11
+ * only the height it filled would happily settle on `Frontmatte` above `r`.
12
+ *
13
+ * So the search starts below the size where any breaking happens. A word that
14
+ * cannot fit even at the floor is broken as it always was, since by then there
15
+ * is nothing else to be done with it.
16
+ *
17
+ * Measuring at the ceiling and scaling is exact rather than a guess: widths are
18
+ * held per em and multiplied by the size, and the estimate used for a font the
19
+ * build cannot read is linear in the size too.
20
+ */
21
+ export declare function largestUnbroken(text: string, measure: MeasureAt, maxWidth: number, ceiling: number): number;
22
+ //# sourceMappingURL=unbroken.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unbroken.d.ts","sourceRoot":"","sources":["../../src/text/unbroken.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE1C;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,SAAS,EAClB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GACd,MAAM,CAUR"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The largest size, no more than `ceiling`, at which every word fits on a line
3
+ * of its own.
4
+ *
5
+ * Text that is being grown to fill a box is the one case where wrapping is
6
+ * under pressure to break a word in half. `wrapText` breaks a word too wide for
7
+ * its line, which is the right answer when the size is settled and the
8
+ * alternative is a line running off the image, and the wrong one here: two
9
+ * lines of a broken word are shorter than one whole one, so a search measuring
10
+ * only the height it filled would happily settle on `Frontmatte` above `r`.
11
+ *
12
+ * So the search starts below the size where any breaking happens. A word that
13
+ * cannot fit even at the floor is broken as it always was, since by then there
14
+ * is nothing else to be done with it.
15
+ *
16
+ * Measuring at the ceiling and scaling is exact rather than a guess: widths are
17
+ * held per em and multiplied by the size, and the estimate used for a font the
18
+ * build cannot read is linear in the size too.
19
+ */
20
+ export function largestUnbroken(text, measure, maxWidth, ceiling) {
21
+ const words = text.split(/\s+/).filter((word) => word !== "");
22
+ return words.reduce((size, word) => {
23
+ const width = measure(word, ceiling);
24
+ return width <= maxWidth
25
+ ? size
26
+ : Math.min(size, Math.floor((ceiling * maxWidth) / width));
27
+ }, ceiling);
28
+ }
29
+ //# sourceMappingURL=unbroken.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unbroken.js","sourceRoot":"","sources":["../../src/text/unbroken.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,eAAe,CAC7B,IAAY,EACZ,OAAkB,EAClB,QAAgB,EAChB,OAAe;IAEf,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC;IAE9D,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE;QACjC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAErC,OAAO,KAAK,IAAI,QAAQ;YACtB,CAAC,CAAC,IAAI;YACN,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,QAAQ,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;IAC/D,CAAC,EAAE,OAAO,CAAC,CAAC;AACd,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { Dimensions, Texture } from "../types.js";
2
+ /**
3
+ * Rows of chevrons: one V to a tile, meeting its neighbours at the tile edge.
4
+ *
5
+ * The V spans the whole width and stops short of the top and bottom, so the
6
+ * rows have air between them. Its ends sit at the same height on both edges,
7
+ * which is what makes one tile's chevron continue into the next rather than
8
+ * ending in a corner.
9
+ */
10
+ export declare function chevronsSvg(texture: Extract<Texture, {
11
+ readonly type: "chevrons";
12
+ }>, dimensions: Dimensions, id: string): string;
13
+ //# sourceMappingURL=chevrons.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chevrons.d.ts","sourceRoot":"","sources":["../../src/texture/chevrons.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAMvD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC,EACxD,UAAU,EAAE,UAAU,EACtB,EAAE,EAAE,MAAM,GACT,MAAM,CAkBR"}
@@ -0,0 +1,22 @@
1
+ import { fallbackColor } from "./color.js";
2
+ import { tile } from "./tile.js";
3
+ const chevronDefaults = { opacity: 0.07, width: 2, gap: 36 };
4
+ /**
5
+ * Rows of chevrons: one V to a tile, meeting its neighbours at the tile edge.
6
+ *
7
+ * The V spans the whole width and stops short of the top and bottom, so the
8
+ * rows have air between them. Its ends sit at the same height on both edges,
9
+ * which is what makes one tile's chevron continue into the next rather than
10
+ * ending in a corner.
11
+ */
12
+ export function chevronsSvg(texture, dimensions, id) {
13
+ const gap = texture.gap ?? chevronDefaults.gap;
14
+ const low = String(gap * 0.7);
15
+ const high = String(gap * 0.3);
16
+ const chevron = `<path d="M0 ${low}L${String(gap / 2)} ${high}L${String(gap)} ${low}"` +
17
+ ` stroke="${texture.color ?? fallbackColor}"` +
18
+ ` stroke-width="${String(texture.width ?? chevronDefaults.width)}"` +
19
+ ` fill="none"/>`;
20
+ return tile(id, gap, chevron, "", dimensions, texture.opacity ?? chevronDefaults.opacity);
21
+ }
22
+ //# sourceMappingURL=chevrons.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chevrons.js","sourceRoot":"","sources":["../../src/texture/chevrons.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,eAAe,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;AAE7D;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CACzB,OAAwD,EACxD,UAAsB,EACtB,EAAU;IAEV,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,eAAe,CAAC,GAAG,CAAC;IAC/C,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC;IAC9B,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC;IAC/B,MAAM,OAAO,GACX,eAAe,GAAG,IAAI,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,IAAI,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG;QACtE,YAAY,OAAO,CAAC,KAAK,IAAI,aAAa,GAAG;QAC7C,kBAAkB,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI,eAAe,CAAC,KAAK,CAAC,GAAG;QACnE,gBAAgB,CAAC;IAEnB,OAAO,IAAI,CACT,EAAE,EACF,GAAG,EACH,OAAO,EACP,EAAE,EACF,UAAU,EACV,OAAO,CAAC,OAAO,IAAI,eAAe,CAAC,OAAO,CAC3C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { Dimensions, Texture } from "../types.js";
2
+ /**
3
+ * Squared paper: lines both ways, and a heavier one every so often.
4
+ *
5
+ * The heavy lines are a second tile rather than part of the first, since what
6
+ * makes them heavy is that they repeat at a multiple of the spacing. Drawing
7
+ * both from `<pattern>`s keeps this among the cheap treatments: the tiles are
8
+ * square to the image, so a row of the rendered picture repeats, which is what
9
+ * the compression works on.
10
+ */
11
+ export declare function gridSvg(texture: Extract<Texture, {
12
+ readonly type: "grid";
13
+ }>, dimensions: Dimensions, id: string): string;
14
+ //# sourceMappingURL=grid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grid.d.ts","sourceRoot":"","sources":["../../src/texture/grid.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AA4BvD;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CACrB,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EACpD,UAAU,EAAE,UAAU,EACtB,EAAE,EAAE,MAAM,GACT,MAAM,CA8BR"}
@@ -0,0 +1,38 @@
1
+ import { fallbackColor } from "./color.js";
2
+ import { tile } from "./tile.js";
3
+ const gridDefaults = { opacity: 0.07, width: 1, gap: 32, major: 5 };
4
+ /** How much heavier the every-so-often line is than the rest. */
5
+ const majorScale = 2;
6
+ /** One square's worth of grid: a line along two of its edges. */
7
+ function square(texture, gap, width) {
8
+ const middle = String(gap / 2);
9
+ const size = String(gap);
10
+ // Down the middle of the tile on both axes rather than along its edges, for
11
+ // the reason `rulesSvg` gives: a stroke centred on the edge would have half
12
+ // of itself outside the tile, where the pattern does not repeat it.
13
+ return (`<path d="M0 ${middle}H${size}M${middle} 0V${size}"` +
14
+ ` stroke="${texture.color ?? fallbackColor}"` +
15
+ ` stroke-width="${String(width)}" fill="none"/>`);
16
+ }
17
+ /**
18
+ * Squared paper: lines both ways, and a heavier one every so often.
19
+ *
20
+ * The heavy lines are a second tile rather than part of the first, since what
21
+ * makes them heavy is that they repeat at a multiple of the spacing. Drawing
22
+ * both from `<pattern>`s keeps this among the cheap treatments: the tiles are
23
+ * square to the image, so a row of the rendered picture repeats, which is what
24
+ * the compression works on.
25
+ */
26
+ export function gridSvg(texture, dimensions, id) {
27
+ const gap = texture.gap ?? gridDefaults.gap;
28
+ const width = texture.width ?? gridDefaults.width;
29
+ const opacity = texture.opacity ?? gridDefaults.opacity;
30
+ const major = texture.major ?? gridDefaults.major;
31
+ const fine = tile(`${id}a`, gap, square(texture, gap, width), "", dimensions, opacity);
32
+ if (major < 2) {
33
+ return fine;
34
+ }
35
+ return (fine +
36
+ tile(`${id}b`, gap * major, square(texture, gap * major, width * majorScale), "", dimensions, opacity));
37
+ }
38
+ //# sourceMappingURL=grid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grid.js","sourceRoot":"","sources":["../../src/texture/grid.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,YAAY,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;AAEpE,iEAAiE;AACjE,MAAM,UAAU,GAAG,CAAC,CAAC;AAErB,iEAAiE;AACjE,SAAS,MAAM,CACb,OAAoD,EACpD,GAAW,EACX,KAAa;IAEb,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IAC/B,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAEzB,4EAA4E;IAC5E,4EAA4E;IAC5E,oEAAoE;IACpE,OAAO,CACL,eAAe,MAAM,IAAI,IAAI,IAAI,MAAM,MAAM,IAAI,GAAG;QACpD,YAAY,OAAO,CAAC,KAAK,IAAI,aAAa,GAAG;QAC7C,kBAAkB,MAAM,CAAC,KAAK,CAAC,iBAAiB,CACjD,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,OAAO,CACrB,OAAoD,EACpD,UAAsB,EACtB,EAAU;IAEV,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,YAAY,CAAC,GAAG,CAAC;IAC5C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,YAAY,CAAC,KAAK,CAAC;IAClD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,YAAY,CAAC,OAAO,CAAC;IACxD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,YAAY,CAAC,KAAK,CAAC;IAElD,MAAM,IAAI,GAAG,IAAI,CACf,GAAG,EAAE,GAAG,EACR,GAAG,EACH,MAAM,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,EAC3B,EAAE,EACF,UAAU,EACV,OAAO,CACR,CAAC;IAEF,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACd,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,CACL,IAAI;QACJ,IAAI,CACF,GAAG,EAAE,GAAG,EACR,GAAG,GAAG,KAAK,EACX,MAAM,CAAC,OAAO,EAAE,GAAG,GAAG,KAAK,EAAE,KAAK,GAAG,UAAU,CAAC,EAChD,EAAE,EACF,UAAU,EACV,OAAO,CACR,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { Dimensions, Texture } from "../types.js";
2
+ /**
3
+ * A grid of dots that grow across the image, which is a gradient made out of
4
+ * print.
5
+ *
6
+ * It cannot be a tile: every dot is a different size, which is the whole
7
+ * effect. What keeps it affordable is that the size depends on the position
8
+ * rather than on chance, so a run of dots across the image is a smooth ramp
9
+ * and there is nothing noisy for the compression to choke on.
10
+ */
11
+ export declare function halftoneSvg(texture: Extract<Texture, {
12
+ readonly type: "halftone";
13
+ }>, dimensions: Dimensions): string;
14
+ //# sourceMappingURL=halftone.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"halftone.d.ts","sourceRoot":"","sources":["../../src/texture/halftone.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAsCvD;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC,EACxD,UAAU,EAAE,UAAU,GACrB,MAAM,CAuBR"}
@@ -0,0 +1,55 @@
1
+ import { fallbackColor } from "./color.js";
2
+ const halftoneDefaults = {
3
+ opacity: 0.12,
4
+ size: 12,
5
+ gap: 30,
6
+ angle: 90,
7
+ from: 0.1,
8
+ };
9
+ /**
10
+ * How far along the growth a point is, from 0 at one edge to 1 at the other.
11
+ *
12
+ * It is the point's distance along the direction the dots grow in, measured
13
+ * from whichever corner is furthest back along it, so the ramp covers the
14
+ * whole image whatever angle it runs at.
15
+ */
16
+ function ramp(dimensions, angle) {
17
+ const radians = (angle * Math.PI) / 180;
18
+ const dx = Math.cos(radians);
19
+ const dy = Math.sin(radians);
20
+ const { width, height } = dimensions;
21
+ // The four corners projected onto the direction give its full span, so the
22
+ // ends of the ramp land on the image rather than somewhere outside it.
23
+ const corners = [0, width].flatMap((cx) => [0, height].map((cy) => cx * dx + cy * dy));
24
+ const low = Math.min(...corners);
25
+ const span = Math.max(...corners) - low || 1;
26
+ return (px, py) => (px * dx + py * dy - low) / span;
27
+ }
28
+ /**
29
+ * A grid of dots that grow across the image, which is a gradient made out of
30
+ * print.
31
+ *
32
+ * It cannot be a tile: every dot is a different size, which is the whole
33
+ * effect. What keeps it affordable is that the size depends on the position
34
+ * rather than on chance, so a run of dots across the image is a smooth ramp
35
+ * and there is nothing noisy for the compression to choke on.
36
+ */
37
+ export function halftoneSvg(texture, dimensions) {
38
+ const gap = texture.gap ?? halftoneDefaults.gap;
39
+ const size = texture.size ?? halftoneDefaults.size;
40
+ const from = texture.from ?? halftoneDefaults.from;
41
+ const at = ramp(dimensions, texture.angle ?? halftoneDefaults.angle);
42
+ let dots = "";
43
+ for (let y = gap / 2; y < dimensions.height + gap; y += gap) {
44
+ for (let x = gap / 2; x < dimensions.width + gap; x += gap) {
45
+ const radius = (size / 2) * (from + (1 - from) * at(x, y));
46
+ dots +=
47
+ `<circle cx="${String(Math.round(x))}" cy="${String(Math.round(y))}"` +
48
+ ` r="${String(Math.round(radius * 100) / 100)}"/>`;
49
+ }
50
+ }
51
+ return (`<g fill="${texture.color ?? fallbackColor}"` +
52
+ ` opacity="${String(texture.opacity ?? halftoneDefaults.opacity)}">` +
53
+ `${dots}</g>`);
54
+ }
55
+ //# sourceMappingURL=halftone.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"halftone.js","sourceRoot":"","sources":["../../src/texture/halftone.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,gBAAgB,GAAG;IACvB,OAAO,EAAE,IAAI;IACb,IAAI,EAAE,EAAE;IACR,GAAG,EAAE,EAAE;IACP,KAAK,EAAE,EAAE;IACT,IAAI,EAAE,GAAG;CACV,CAAC;AAEF;;;;;;GAMG;AACH,SAAS,IAAI,CACX,UAAsB,EACtB,KAAa;IAEb,MAAM,OAAO,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC;IACxC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC7B,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC7B,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC;IAErC,2EAA2E;IAC3E,uEAAuE;IACvE,MAAM,OAAO,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CACxC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAC3C,CAAC;IACF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC;IACjC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC;IAE7C,OAAO,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC;AACtD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CACzB,OAAwD,EACxD,UAAsB;IAEtB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,gBAAgB,CAAC,GAAG,CAAC;IAChD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,gBAAgB,CAAC,IAAI,CAAC;IACnD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,gBAAgB,CAAC,IAAI,CAAC;IACnD,MAAM,EAAE,GAAG,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,KAAK,IAAI,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAErE,IAAI,IAAI,GAAG,EAAE,CAAC;IAEd,KAAK,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC,IAAI,GAAG,EAAE,CAAC;QAC5D,KAAK,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,KAAK,GAAG,GAAG,EAAE,CAAC,IAAI,GAAG,EAAE,CAAC;YAC3D,MAAM,MAAM,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YAE3D,IAAI;gBACF,eAAe,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG;oBACrE,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC;QACvD,CAAC;IACH,CAAC;IAED,OAAO,CACL,YAAY,OAAO,CAAC,KAAK,IAAI,aAAa,GAAG;QAC7C,aAAa,MAAM,CAAC,OAAO,CAAC,OAAO,IAAI,gBAAgB,CAAC,OAAO,CAAC,IAAI;QACpE,GAAG,IAAI,MAAM,CACd,CAAC;AACJ,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { Dimensions, Texture } from "../types.js";
2
+ /**
3
+ * A honeycomb of hexagon outlines.
4
+ *
5
+ * The grid repeats every three sides across and every `side * sqrt(3)` down,
6
+ * which is the smallest rectangle a honeycomb fits in. Two hexagons go in it,
7
+ * the second offset by half of each, and the ones that fall outside are drawn
8
+ * anyway so that the tile joins its neighbours: the pattern clips them, and a
9
+ * hexagon cut in half by the tile edge meets its other half in the next tile.
10
+ */
11
+ export declare function honeycombSvg(texture: Extract<Texture, {
12
+ readonly type: "honeycomb";
13
+ }>, dimensions: Dimensions, id: string): string;
14
+ //# sourceMappingURL=honeycomb.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"honeycomb.d.ts","sourceRoot":"","sources":["../../src/texture/honeycomb.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AA8BvD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,EACzD,UAAU,EAAE,UAAU,EACtB,EAAE,EAAE,MAAM,GACT,MAAM,CAwBR"}
@@ -0,0 +1,43 @@
1
+ import { fallbackColor } from "./color.js";
2
+ import { tile } from "./tile.js";
3
+ const combDefaults = { opacity: 0.07, width: 1.5, size: 26 };
4
+ /** Rounded to keep the path short, since it is written out twice a tile. */
5
+ function at(value) {
6
+ return String(Math.round(value * 100) / 100);
7
+ }
8
+ /**
9
+ * One flat-topped hexagon, from its centre and the length of a side.
10
+ *
11
+ * A hexagon of side `s` is `2s` across the points and `s * sqrt(3)` across the
12
+ * flats, which is where every number below comes from.
13
+ */
14
+ function hexagon(cx, cy, side) {
15
+ const half = (side * Math.sqrt(3)) / 2;
16
+ return (`M${at(cx - side)} ${at(cy)}` +
17
+ `L${at(cx - side / 2)} ${at(cy - half)}` +
18
+ `L${at(cx + side / 2)} ${at(cy - half)}` +
19
+ `L${at(cx + side)} ${at(cy)}` +
20
+ `L${at(cx + side / 2)} ${at(cy + half)}` +
21
+ `L${at(cx - side / 2)} ${at(cy + half)}Z`);
22
+ }
23
+ /**
24
+ * A honeycomb of hexagon outlines.
25
+ *
26
+ * The grid repeats every three sides across and every `side * sqrt(3)` down,
27
+ * which is the smallest rectangle a honeycomb fits in. Two hexagons go in it,
28
+ * the second offset by half of each, and the ones that fall outside are drawn
29
+ * anyway so that the tile joins its neighbours: the pattern clips them, and a
30
+ * hexagon cut in half by the tile edge meets its other half in the next tile.
31
+ */
32
+ export function honeycombSvg(texture, dimensions, id) {
33
+ const side = texture.size ?? combDefaults.size;
34
+ const height = side * Math.sqrt(3);
35
+ const width = side * 3;
36
+ const comb = `<path d="${hexagon(side, height / 2, side)}${hexagon(side * 2.5, 0, side)}${hexagon(side * 2.5, height, side)}" stroke="${texture.color ?? fallbackColor}"` +
37
+ ` stroke-width="${String(texture.width ?? combDefaults.width)}"` +
38
+ ` fill="none"/>`;
39
+ // Not the square tile the others use: a honeycomb's repeat is wider than it
40
+ // is tall, so `tile` takes the two separately here.
41
+ return tile(id, { width, height }, comb, "", dimensions, texture.opacity ?? combDefaults.opacity);
42
+ }
43
+ //# sourceMappingURL=honeycomb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"honeycomb.js","sourceRoot":"","sources":["../../src/texture/honeycomb.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,YAAY,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;AAE7D,4EAA4E;AAC5E,SAAS,EAAE,CAAC,KAAa;IACvB,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC;AAC/C,CAAC;AAED;;;;;GAKG;AACH,SAAS,OAAO,CAAC,EAAU,EAAE,EAAU,EAAE,IAAY;IACnD,MAAM,IAAI,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAEvC,OAAO,CACL,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE;QAC7B,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE;QACxC,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE;QACxC,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE;QAC7B,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE;QACxC,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,CAC1C,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAC1B,OAAyD,EACzD,UAAsB,EACtB,EAAU;IAEV,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,YAAY,CAAC,IAAI,CAAC;IAC/C,MAAM,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC;IAEvB,MAAM,IAAI,GACR,YAAY,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,GAAG,OAAO,CACnD,IAAI,GAAG,GAAG,EACV,CAAC,EACD,IAAI,CACL,GAAG,OAAO,CAAC,IAAI,GAAG,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,aAAa,OAAO,CAAC,KAAK,IAAI,aAAa,GAAG;QACnF,kBAAkB,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI,YAAY,CAAC,KAAK,CAAC,GAAG;QAChE,gBAAgB,CAAC;IAEnB,4EAA4E;IAC5E,oDAAoD;IACpD,OAAO,IAAI,CACT,EAAE,EACF,EAAE,KAAK,EAAE,MAAM,EAAE,EACjB,IAAI,EACJ,EAAE,EACF,UAAU,EACV,OAAO,CAAC,OAAO,IAAI,YAAY,CAAC,OAAO,CACxC,CAAC;AACJ,CAAC"}
@@ -1,12 +1,23 @@
1
1
  import type { Dimensions, Texture } from "../types.js";
2
- export { resolveTexture } from "./resolve.js";
2
+ export { resolveTexture, resolveTextureScale } from "./resolve.js";
3
3
  /**
4
4
  * Render the treatment laid over a background as SVG, naming whatever it needs
5
- * in `<defs>` after the given `id`. Not every treatment needs one: `waves` is
6
- * plain elements and names nothing.
5
+ * in `<defs>` after the given `id`.
7
6
  *
8
7
  * It is drawn between the background and whatever a template draws, so a
9
8
  * texture never comes between a headline and the reader.
9
+ *
10
+ * `scale` draws the same picture larger, for an image that will be looked at
11
+ * much smaller than it was rendered. It is done here, once, rather than by
12
+ * every treatment reading it: the thirteen of them describe their geometry in
13
+ * a dozen different fields, and there is no arithmetic they could share except
14
+ * this. So the texture is drawn into a smaller image and that image is scaled
15
+ * back up, which is exactly what a magnifier over the corner of it would show.
16
+ * The dimensions are rounded up so the treatment still covers the last row of
17
+ * pixels, and the viewport is what trims the overhang.
18
+ *
19
+ * A scale of `1` writes what it always wrote, down to the byte, which is what
20
+ * leaves every image that does not ask for this alone.
10
21
  */
11
- export declare function textureSvg(texture: Texture, dimensions: Dimensions, id: string): string;
22
+ export declare function textureSvg(texture: Texture, dimensions: Dimensions, id: string, scale?: number): string;
12
23
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/texture/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAKvD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C;;;;;;;GAOG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,UAAU,EACtB,EAAE,EAAE,MAAM,GACT,MAAM,CAcR"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/texture/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAavD,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAuCnE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,UAAU,EACtB,EAAE,EAAE,MAAM,EACV,KAAK,SAAI,GACR,MAAM,CAgBR"}
@@ -1,25 +1,75 @@
1
+ import { chevronsSvg } from "./chevrons.js";
1
2
  import { grainSvg } from "./grain.js";
2
- import { dotsSvg, rulesSvg } from "./pattern.js";
3
+ import { halftoneSvg } from "./halftone.js";
4
+ import { gridSvg } from "./grid.js";
5
+ import { honeycombSvg } from "./honeycomb.js";
6
+ import { moireSvg } from "./moire.js";
7
+ import { crossesSvg, dotsSvg, rulesSvg } from "./pattern.js";
8
+ import { raysSvg } from "./rays.js";
9
+ import { scallopsSvg } from "./scallops.js";
10
+ import { topographicSvg } from "./topographic/index.js";
3
11
  import { wavesSvg } from "./waves.js";
4
- export { resolveTexture } from "./resolve.js";
12
+ export { resolveTexture, resolveTextureScale } from "./resolve.js";
13
+ /**
14
+ * Which function draws which treatment.
15
+ *
16
+ * A table rather than a chain of `if`s, because the chain had a branch per
17
+ * texture and there are ten of them: the file was close to the complexity
18
+ * threshold and every new treatment made it worse. The type is what keeps this
19
+ * honest, and it does the job the chain's final `return` used to do badly: a
20
+ * variant added to `Texture` with no entry here fails to compile, where before
21
+ * it silently came out as ruled lines.
22
+ *
23
+ * `waves` and `rays` name nothing in `<defs>` and take no id. They are wrapped
24
+ * rather than given a parameter to ignore, so each one's own signature still
25
+ * says what it uses.
26
+ */
27
+ const drawers = {
28
+ grain: grainSvg,
29
+ dots: dotsSvg,
30
+ rules: rulesSvg,
31
+ grid: gridSvg,
32
+ crosses: crossesSvg,
33
+ chevrons: chevronsSvg,
34
+ honeycomb: honeycombSvg,
35
+ moire: moireSvg,
36
+ scallops: scallopsSvg,
37
+ waves: (texture, dimensions) => wavesSvg(texture, dimensions),
38
+ rays: (texture, dimensions) => raysSvg(texture, dimensions),
39
+ halftone: (texture, dimensions) => halftoneSvg(texture, dimensions),
40
+ topographic: (texture, dimensions) => topographicSvg(texture, dimensions),
41
+ };
5
42
  /**
6
43
  * Render the treatment laid over a background as SVG, naming whatever it needs
7
- * in `<defs>` after the given `id`. Not every treatment needs one: `waves` is
8
- * plain elements and names nothing.
44
+ * in `<defs>` after the given `id`.
9
45
  *
10
46
  * It is drawn between the background and whatever a template draws, so a
11
47
  * texture never comes between a headline and the reader.
48
+ *
49
+ * `scale` draws the same picture larger, for an image that will be looked at
50
+ * much smaller than it was rendered. It is done here, once, rather than by
51
+ * every treatment reading it: the thirteen of them describe their geometry in
52
+ * a dozen different fields, and there is no arithmetic they could share except
53
+ * this. So the texture is drawn into a smaller image and that image is scaled
54
+ * back up, which is exactly what a magnifier over the corner of it would show.
55
+ * The dimensions are rounded up so the treatment still covers the last row of
56
+ * pixels, and the viewport is what trims the overhang.
57
+ *
58
+ * A scale of `1` writes what it always wrote, down to the byte, which is what
59
+ * leaves every image that does not ask for this alone.
12
60
  */
13
- export function textureSvg(texture, dimensions, id) {
14
- if (texture.type === "grain") {
15
- return grainSvg(texture, dimensions, id);
16
- }
17
- if (texture.type === "dots") {
18
- return dotsSvg(texture, dimensions, id);
19
- }
20
- if (texture.type === "waves") {
21
- return wavesSvg(texture, dimensions);
61
+ export function textureSvg(texture, dimensions, id, scale = 1) {
62
+ // The value and the lookup narrow separately, so the compiler cannot see
63
+ // that they are the same variant, though the table's type is what makes them
64
+ // so. This is the one line that knows better than the checker.
65
+ const draw = drawers[texture.type];
66
+ if (scale === 1) {
67
+ return draw(texture, dimensions, id);
22
68
  }
23
- return rulesSvg(texture, dimensions, id);
69
+ const reduced = {
70
+ width: Math.ceil(dimensions.width / scale),
71
+ height: Math.ceil(dimensions.height / scale),
72
+ };
73
+ return `<g transform="scale(${String(scale)})">${draw(texture, reduced, id)}</g>`;
24
74
  }
25
75
  //# sourceMappingURL=index.js.map