@nxtedition/lib 22.0.14 → 22.0.15

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 (2) hide show
  1. package/package.json +3 -2
  2. package/wordwrap.js +128 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/lib",
3
- "version": "22.0.14",
3
+ "version": "22.0.15",
4
4
  "license": "MIT",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "type": "module",
@@ -34,7 +34,8 @@
34
34
  "worker.js",
35
35
  "stream.js",
36
36
  "timeline.js",
37
- "docker-secrets.js"
37
+ "docker-secrets.js",
38
+ "wordwrap.js"
38
39
  ],
39
40
  "scripts": {
40
41
  "prepublishOnly": "pinst --disable",
package/wordwrap.js ADDED
@@ -0,0 +1,128 @@
1
+ // @ts-check
2
+ import { createCanvas } from 'canvas'
3
+
4
+ const canvas = createCanvas(0, 0)
5
+ const ctx = canvas.getContext('2d')
6
+
7
+ /**
8
+ * Takes a string and a maxWidth and splits the text into lines.
9
+ *
10
+ * @param {string} text
11
+ * @param {number} fontSize
12
+ * @param {string} fontFamily
13
+ * @param {number} maxWidth
14
+ */
15
+ export function wordwrap(text, fontSize, fontFamily, maxWidth) {
16
+ // TODO: Register fonts
17
+ // const fontFile = name =>
18
+ // path.join(path.dirname(__dirname), 'fonts', `${name}.ttf`)
19
+ // const ptSans = new Canvas.Font('PTSans', fontFile('ptsans-regular'))
20
+ // ctx.addFont(ptSans)
21
+
22
+ ctx.font = `${fontSize}px ${fontFamily}`
23
+ const emMeasure = ctx.measureText('M').width
24
+ const spaceMeasure = ctx.measureText(' ').width
25
+
26
+ if (maxWidth < emMeasure) {
27
+ // To prevent weird looping anamolies farther on.
28
+ return text.split('')
29
+ }
30
+
31
+ if (ctx.measureText(text).width < maxWidth) {
32
+ return [text]
33
+ }
34
+
35
+ const words = text.split(' ')
36
+ const metawords = []
37
+ const lines = []
38
+
39
+ // measure first.
40
+ for (const w in words) {
41
+ const word = words[w]
42
+ const measure = ctx.measureText(word).width
43
+
44
+ // Edge case - If the current word is too long for one line, break it into maximized pieces.
45
+ if (measure > maxWidth) {
46
+ // TODO - a divide and conquer method might be nicer.
47
+ const edgewords = splitWord(word, maxWidth)
48
+
49
+ // could use metawords = metawords.concat(edgwords)
50
+ for (const ew in edgewords) {
51
+ metawords.push(edgewords[ew])
52
+ }
53
+ } else {
54
+ metawords.push({ word, len: word.length, measure })
55
+ }
56
+ }
57
+
58
+ // build array of lines second.
59
+ let cline = ''
60
+ let cmeasure = 0
61
+ for (const mw in metawords) {
62
+ const metaword = metawords[mw]
63
+
64
+ // If current word doesn't fit on current line, push the current line and start a new one.
65
+ // Unless (edge-case): this is a new line and the current word is one character.
66
+ if (cmeasure + metaword.measure > maxWidth && cmeasure > 0 && metaword.len > 1) {
67
+ lines.push(cline)
68
+ cline = ''
69
+ cmeasure = 0
70
+ }
71
+
72
+ cline += metaword.word
73
+ cmeasure += metaword.measure
74
+
75
+ // If there's room, append a space, else push the current line and start a new one.
76
+ if (cmeasure + spaceMeasure < maxWidth) {
77
+ cline += ' '
78
+ cmeasure += spaceMeasure
79
+ } else {
80
+ lines.push(cline)
81
+ cline = ''
82
+ cmeasure = 0
83
+ }
84
+ }
85
+ if (cmeasure > 0) {
86
+ lines.push(cline)
87
+ }
88
+
89
+ return lines
90
+ }
91
+
92
+ /**
93
+ * @param {string} word
94
+ * @param {number} maxWidth
95
+ */
96
+ function splitWord(word, maxWidth) {
97
+ const wlen = word.length
98
+ if (wlen === 0) {
99
+ return []
100
+ }
101
+
102
+ const awords = []
103
+ const letters = []
104
+ let cword = ''
105
+ let cmeasure = 0
106
+
107
+ // Measure each letter.
108
+ for (let l = 0; l < wlen; l += 1) {
109
+ letters.push({ letter: word[l], measure: ctx.measureText(word[l]).width })
110
+ }
111
+
112
+ // Assemble the letters into words of maximized length.
113
+ for (const ml in letters) {
114
+ const metaletter = letters[ml]
115
+
116
+ if (cmeasure + metaletter.measure > maxWidth) {
117
+ awords.push({ word: cword, len: cword.length, measure: cmeasure })
118
+ cword = ''
119
+ cmeasure = 0
120
+ }
121
+
122
+ cword += metaletter.letter
123
+ cmeasure += metaletter.measure
124
+ }
125
+ // there will always be one more word to push.
126
+ awords.push({ word: cword, len: cword.length, measure: cmeasure })
127
+ return awords
128
+ }