diff --git a/README.md b/README.md
index c1432fb..5c149bb 100644
--- a/README.md
+++ b/README.md
@@ -39,7 +39,11 @@ This layer contains code to interact with printers
- [TSPL](documentations/TSPL.pdf)
-### Usefull units:
+# Usefull units:
- 1 pt = 1/72 inch
-- 1 dot = 1 / dpi
\ No newline at end of file
+- 1 dot = 1 / dpi
+
+# Notes
+
+- If a font is not working, make sure the extension is TTF
\ No newline at end of file
diff --git a/package.json b/package.json
index 536f34d..e573c91 100644
--- a/package.json
+++ b/package.json
@@ -25,7 +25,9 @@
"repository": "https://github.com/kemkriszt/raw-thermal-print.git",
"devDependencies": {
"@changesets/cli": "^2.27.1",
+ "@types/fontkit": "^2.0.6",
"@types/jest": "^29.5.11",
+ "@types/w3c-web-usb": "^1.0.10",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.2",
@@ -34,9 +36,9 @@
"typescript": "^5.3.3"
},
"dependencies": {
- "@types/w3c-web-usb": "^1.0.10",
- "clean-html": "^2.0.1",
+ "fontkit": "^2.0.2",
"image-pixels": "^2.2.2",
+ "node-html-parser": "^6.1.12",
"usb": "^2.11.0"
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f4bd8dd..e364f0d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -5,15 +5,15 @@ settings:
excludeLinksFromLockfile: false
dependencies:
- '@types/w3c-web-usb':
- specifier: ^1.0.10
- version: 1.0.10
- clean-html:
- specifier: ^2.0.1
- version: 2.0.1
+ fontkit:
+ specifier: ^2.0.2
+ version: 2.0.2
image-pixels:
specifier: ^2.2.2
version: 2.2.2
+ node-html-parser:
+ specifier: ^6.1.12
+ version: 6.1.12
usb:
specifier: ^2.11.0
version: 2.11.0
@@ -22,9 +22,15 @@ devDependencies:
'@changesets/cli':
specifier: ^2.27.1
version: 2.27.1
+ '@types/fontkit':
+ specifier: ^2.0.6
+ version: 2.0.6
'@types/jest':
specifier: ^29.5.11
version: 29.5.11
+ '@types/w3c-web-usb':
+ specifier: ^1.0.10
+ version: 1.0.10
jest:
specifier: ^29.7.0
version: 29.7.0(@types/node@20.10.4)(ts-node@10.9.2)
@@ -1225,6 +1231,19 @@ packages:
'@sinonjs/commons': 3.0.0
dev: true
+ /@swc/helpers@0.4.14:
+ resolution: {integrity: sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==}
+ dependencies:
+ tslib: 2.6.2
+ dev: false
+
+ /@swc/helpers@0.4.36:
+ resolution: {integrity: sha512-5lxnyLEYFskErRPenYItLRSge5DjrJngYKdVjRSrWfza9G6KkgHEXi0vUZiyUeMU5JfXH1YnvXZzSp8ul88o2Q==}
+ dependencies:
+ legacy-swc-helpers: /@swc/helpers@0.4.14
+ tslib: 2.6.2
+ dev: false
+
/@tsconfig/node10@1.0.9:
resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==}
dev: true
@@ -1270,6 +1289,12 @@ packages:
'@babel/types': 7.23.5
dev: true
+ /@types/fontkit@2.0.6:
+ resolution: {integrity: sha512-GxeaJay2RidoCWRDogp2CRttkv0weLwrB7hC+Qmh0mCdW1g0aEVwlLA6kLv5ZDJ6hGZSEiGZG7r57MgAD+fWOg==}
+ dependencies:
+ '@types/node': 20.10.4
+ dev: true
+
/@types/graceful-fs@4.1.9:
resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==}
dependencies:
@@ -1327,7 +1352,6 @@ packages:
/@types/w3c-web-usb@1.0.10:
resolution: {integrity: sha512-CHgUI5kTc/QLMP8hODUHhge0D4vx+9UiAwIGiT0sTy/B2XpdX1U5rJt6JSISgr6ikRT7vxV9EVAFeYZqUnl1gQ==}
- dev: false
/@types/yargs-parser@21.0.3:
resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==}
@@ -1584,6 +1608,10 @@ packages:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: true
+ /base64-js@1.5.1:
+ resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
+ dev: false
+
/bcrypt-pbkdf@1.0.2:
resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==}
dependencies:
@@ -1606,6 +1634,10 @@ packages:
resolution: {integrity: sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==}
dev: false
+ /boolbase@1.0.0:
+ resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
+ dev: false
+
/brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
dependencies:
@@ -1632,6 +1664,12 @@ packages:
wcwidth: 1.0.1
dev: true
+ /brotli@1.3.3:
+ resolution: {integrity: sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==}
+ dependencies:
+ base64-js: 1.5.1
+ dev: false
+
/browserslist@4.22.2:
resolution: {integrity: sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
@@ -1773,14 +1811,6 @@ packages:
resolution: {integrity: sha512-kgMuFyE78OC6Dyu3Dy7vcx4uy97EIbVxJB/B0eJ3bUNAkwdNcxYzgKltnyADiYwsR7SEqkkUPsEUT//OVS6XMA==}
dev: false
- /clean-html@2.0.1:
- resolution: {integrity: sha512-8nqCWku8DLhZfDAtb6QRoRvT2o5BOW9nh8QlmpS2ujkyEz5TBDHNIAvL4PK+mNp52ir6zm5pAJJ3XOYtBe4mqw==}
- hasBin: true
- dependencies:
- htmlparser2: 8.0.2
- minimist: 1.2.8
- dev: false
-
/clip-pixels@1.0.1:
resolution: {integrity: sha512-nJ22fZvCwkJfMppkOEE7GciLX08rDnVzEJ+U46kBFZtwNzH2V4tNxMWa9Tc365WspCxy1c3NtGJ5EeT4SgjmCA==}
dev: false
@@ -1807,6 +1837,11 @@ packages:
engines: {node: '>=0.8'}
dev: true
+ /clone@2.1.2:
+ resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==}
+ engines: {node: '>=0.8'}
+ dev: false
+
/co@4.6.0:
resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
@@ -1919,6 +1954,21 @@ packages:
which: 2.0.2
dev: true
+ /css-select@5.1.0:
+ resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==}
+ dependencies:
+ boolbase: 1.0.0
+ css-what: 6.1.0
+ domhandler: 5.0.3
+ domutils: 3.1.0
+ nth-check: 2.1.1
+ dev: false
+
+ /css-what@6.1.0:
+ resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
+ engines: {node: '>= 6'}
+ dev: false
+
/csv-generate@3.4.3:
resolution: {integrity: sha512-w/T+rqR0vwvHqWs/1ZyMDWtHHSJaN06klRqJXBEpDJaM/+dZkso0OKh1VcuuYvK3XM53KysVNq8Ko/epCK8wOw==}
dev: true
@@ -2026,6 +2076,10 @@ packages:
engines: {node: '>=8'}
dev: true
+ /dfa@1.2.0:
+ resolution: {integrity: sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==}
+ dev: false
+
/diff-sequences@29.6.3:
resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -2370,6 +2424,20 @@ packages:
resolution: {integrity: sha512-oXbJGbjDnfJRWPC7Va38EFhd+A8JWE5/hCiKcK8qjCdbLj9DTpsq6MEudwpRTH+V4qq+Jw7d3pUgQdSr3x3mTA==}
dev: false
+ /fontkit@2.0.2:
+ resolution: {integrity: sha512-jc4k5Yr8iov8QfS6u8w2CnHWVmbOGtdBtOXMze5Y+QD966Rx6PEVWXSEGwXlsDlKtu1G12cJjcsybnqhSk/+LA==}
+ dependencies:
+ '@swc/helpers': 0.4.36
+ brotli: 1.3.3
+ clone: 2.1.2
+ dfa: 1.2.0
+ fast-deep-equal: 3.1.3
+ restructure: 3.0.0
+ tiny-inflate: 1.0.3
+ unicode-properties: 1.4.1
+ unicode-trie: 2.0.0
+ dev: false
+
/for-each@0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
dependencies:
@@ -2619,6 +2687,11 @@ packages:
function-bind: 1.1.2
dev: true
+ /he@1.2.0:
+ resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
+ hasBin: true
+ dev: false
+
/hosted-git-info@2.8.9:
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
dev: true
@@ -2627,15 +2700,6 @@ packages:
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
dev: true
- /htmlparser2@8.0.2:
- resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
- dependencies:
- domelementtype: 2.3.0
- domhandler: 5.0.3
- domutils: 3.1.0
- entities: 4.5.0
- dev: false
-
/http-signature@1.2.0:
resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==}
engines: {node: '>=0.8', npm: '>=1.3.7'}
@@ -3715,6 +3779,7 @@ packages:
/minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+ dev: true
/minipass@7.0.4:
resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==}
@@ -3751,6 +3816,13 @@ packages:
hasBin: true
dev: false
+ /node-html-parser@6.1.12:
+ resolution: {integrity: sha512-/bT/Ncmv+fbMGX96XG9g05vFt43m/+SYKIs9oAemQVYyVcZmDAI2Xq/SbNcpOA35eF0Zk2av3Ksf+Xk8Vt8abA==}
+ dependencies:
+ css-select: 5.1.0
+ he: 1.2.0
+ dev: false
+
/node-int64@0.4.0:
resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
dev: true
@@ -3780,6 +3852,12 @@ packages:
path-key: 3.1.1
dev: true
+ /nth-check@2.1.1:
+ resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
+ dependencies:
+ boolbase: 1.0.0
+ dev: false
+
/oauth-sign@0.9.0:
resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==}
dev: false
@@ -3877,6 +3955,10 @@ packages:
engines: {node: '>=6'}
dev: true
+ /pako@0.2.9:
+ resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==}
+ dev: false
+
/pako@1.0.11:
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
dev: false
@@ -4190,6 +4272,10 @@ packages:
supports-preserve-symlinks-flag: 1.0.0
dev: true
+ /restructure@3.0.0:
+ resolution: {integrity: sha512-Xj8/MEIhhfj9X2rmD9iJ4Gga9EFqVlpMj3vfLnV2r/Mh5jRMryNV+6lWh9GdJtDBcBSPIqzRdfBQ3wDtNFv/uw==}
+ dev: false
+
/reusify@1.0.4:
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
@@ -4598,6 +4684,10 @@ packages:
any-promise: 1.3.0
dev: true
+ /tiny-inflate@1.0.3:
+ resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==}
+ dev: false
+
/tmp@0.0.33:
resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==}
engines: {node: '>=0.6.0'}
@@ -4742,6 +4832,10 @@ packages:
strip-bom: 3.0.0
dev: true
+ /tslib@2.6.2:
+ resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
+ dev: false
+
/tsup@8.0.1(ts-node@10.9.2)(typescript@5.3.3):
resolution: {integrity: sha512-hvW7gUSG96j53ZTSlT4j/KL0q1Q2l6TqGBFc6/mu/L46IoNWqLLUzLRLP1R8Q7xrJTmkDxxDoojV5uCVs1sVOg==}
engines: {node: '>=18'}
@@ -4896,6 +4990,20 @@ packages:
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
dev: true
+ /unicode-properties@1.4.1:
+ resolution: {integrity: sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==}
+ dependencies:
+ base64-js: 1.5.1
+ unicode-trie: 2.0.0
+ dev: false
+
+ /unicode-trie@2.0.0:
+ resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==}
+ dependencies:
+ pako: 0.2.9
+ tiny-inflate: 1.0.3
+ dev: false
+
/universalify@0.1.2:
resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
engines: {node: '>= 4.0.0'}
diff --git a/src/commands/tspl/commands/basic/TSPLDisplay.ts b/src/commands/tspl/commands/basic/TSPLDisplay.ts
index 9ea8c3f..c782b57 100644
--- a/src/commands/tspl/commands/basic/TSPLDisplay.ts
+++ b/src/commands/tspl/commands/basic/TSPLDisplay.ts
@@ -1,6 +1,6 @@
import TSPLCommand from "../../TSPLCommand";
-export type DisplayType = "CLS"|"IMAGE"
+export type DisplayType = "CLS"|"IMAGE"|"OFF"
/**
* Displays the image buffer on the screen
diff --git a/src/examples/node/index.ts b/src/examples/node/index.ts
index b159127..a2731b8 100644
--- a/src/examples/node/index.ts
+++ b/src/examples/node/index.ts
@@ -2,29 +2,63 @@ import { Label } from "@/labels"
import { Line, Text } from "@/labels/fields"
import { PrinterService } from "@/printers"
import fs from "fs"
+import { NodeType, parse, HTMLElement } from "node-html-parser"
+import fontkit from "fontkit"
export default async () => {
+ // const rootNode = parse("Some cool text first u Other remaining u cool asd asd qwe")
+ // rootNode.childNodes.forEach(node => {
+ // if(node.nodeType == NodeType.TEXT_NODE) {
+ // console.log("T (", node.innerText, ")")
+ // } else if(node.nodeType == NodeType.ELEMENT_NODE) {
+ // const casted = node as HTMLElement
+ // console.log()
+ // console.log(casted.rawTagName, " ----> ")
+
+ // casted.childNodes.forEach((element) => {
+ // if(element.nodeType == NodeType.TEXT_NODE) {
+ // console.log("T (", element.innerText, ")")
+ // } else if(element.nodeType == NodeType.ELEMENT_NODE) {
+ // const casted2 = element as HTMLElement
+ // console.log(casted2.rawTagName, " (", element.innerText, ")")
+ // }
+ // })
+
+ // console.log(casted.rawTagName, " <---- ")
+ // console.log()
+ // }
+ // })
+
const printers = await PrinterService.getPrinters()
console.log("Printers", printers)
if(printers.length > 0) {
const printer = printers[0]
- const fontName = "super.TTF"
- const testText = "Hello 4"
- const fontSize = 40
+ const fontName = "super"
+ const fontName2 = "mathova"
+ const testText = "Hello 4 from the other side"
+ const fontSize = 25
const textX = 10
const textY = 10
- const font = fs.readFileSync(__dirname+"/"+fontName).buffer
+ const font = fs.readFileSync(__dirname+"/"+fontName+".TTF").buffer
+ const font2 = fs.readFileSync(__dirname+"/"+fontName2+".TTF").buffer
const label = new Label(50, 25)
- const text = new Text(testText, textX, textY)
- const line = new Line({x: textX, y: textY + fontSize}, {x: textX + 100, y: textY + fontSize})
- text.setFont(fontName, fontSize)
label.registerFont(font, fontName)
- label.add(text, line)
+ label.registerFont(font2, fontName2)
+
+ const text = new Text(testText, textX, textY, true)
+ // const line = new Line({x: textX, y: textY + fontSize}, {x: textX , y: textY + fontSize})
+ const line2 = new Line({x: textX - 5, y: textY + fontSize}, {x: textX - 5, y: textY})
+
+ text.setFont(fontName2, fontSize)
+ text.setMultiLine(150)
+
+
+ label.add(text) // line2
await printer.display(label)
await printer.close()
diff --git a/src/examples/node/mathova.ttf b/src/examples/node/mathova.ttf
new file mode 100644
index 0000000..c418f73
Binary files /dev/null and b/src/examples/node/mathova.ttf differ
diff --git a/src/helpers/StringUtils.ts b/src/helpers/StringUtils.ts
index bff5405..667c621 100644
--- a/src/helpers/StringUtils.ts
+++ b/src/helpers/StringUtils.ts
@@ -20,4 +20,6 @@ export default class StringUtils {
let decoder = new TextDecoder()
return decoder.decode(bytes)
}
-}
\ No newline at end of file
+}
+
+export const isWhitespace = (text: string) => text.trim() === ""
\ No newline at end of file
diff --git a/src/helpers/UnitUtils.ts b/src/helpers/UnitUtils.ts
index 3062c74..da4565f 100644
--- a/src/helpers/UnitUtils.ts
+++ b/src/helpers/UnitUtils.ts
@@ -16,4 +16,18 @@ export function valueWithUnit(value: number, unitSystem: UnitSystem) {
export function dotToPoint(dots: number, dpi: number): number {
const inch = dots / dpi
return Math.round(inch * pointsPerInch)
+}
+
+/**
+ * Converts the points value to dots
+ * 1 inch = 72 points (standard in typography)
+ * Formula: dots = points * dpi / pointsPerInch
+ * @param points
+ * @param dpi
+ * @returns
+ */
+export function pointsToDots(points: number, dpi: number): number {
+ const pointsPerInch = 72;
+ const dots = points * dpi / pointsPerInch;
+ return dots;
}
\ No newline at end of file
diff --git a/src/labels/Label.ts b/src/labels/Label.ts
index 94d3c22..6952c59 100644
--- a/src/labels/Label.ts
+++ b/src/labels/Label.ts
@@ -1,10 +1,12 @@
import { Command, PrinterLanguage } from "@/commands";
import Printable, { PrintConfig } from "./Printable";
import { UnitSystem } from "@/commands";
-import { LabelDirection, TSPLCommand, TSPLRawCommand } from "@/commands/tspl";
+import { LabelDirection } from "@/commands/tspl";
import LabelField from "./fields/LabelField";
import { Font } from "./types";
import CommandGenerator from "@/commands/CommandGenerator";
+import fontkit from "fontkit"
+import { dotToPoint, pointsToDots } from "@/helpers/UnitUtils";
/**
* Holds the content of a label and handles printing
@@ -23,7 +25,7 @@ export default class Label extends Printable {
* Units for width, height, gap and offset
*/
private readonly unitSystem: UnitSystem
- private fonts: Font[] = []
+ private fonts: Record = {}
private dpi: number
/**
@@ -34,8 +36,19 @@ export default class Label extends Printable {
/**
* Configuration used when generating commands
*/
- private get printConfig(): PrintConfig {
- return { dpi: this.dpi }
+ get printConfig(): PrintConfig {
+ return {
+ dpi: this.dpi,
+ textWidth: (text, font, fontSize) => {
+ const size = dotToPoint(fontSize, this.dpi)
+ const fontObject = this.fonts[font].font
+
+ const run = fontObject.layout(text)
+
+ const scaledWidth = size * run.advanceWidth / fontObject.unitsPerEm
+ return pointsToDots(scaledWidth, this.dpi)
+ }
+ }
}
constructor(width: number, height: number, dimensionUnit: UnitSystem = "metric", dpi: number = 203) {
@@ -71,7 +84,8 @@ export default class Label extends Printable {
file = await resp.arrayBuffer()
}
- this.fonts.push({name, data: file})
+ const fontBuffer = Buffer.from(file)
+ this.fonts[name] = {name, data: file, font: fontkit.create(fontBuffer)}
}
/**
@@ -129,7 +143,7 @@ export default class Label extends Printable {
gapOffset: number = 0,
generator: CommandGenerator) {
const commands = [
- ...(this.fonts.map((font) => generator.upload(font.name, font.data) )),
+ ...(Object.values(this.fonts).map((font) => generator.upload(font.name+".TTF", font.data) )),
generator.setUp(this.width, this.height, gap, gapOffset, direction, mirror, this.unitSystem),
(await this.commandForLanguage(language, this.printConfig)),
]
diff --git a/src/labels/Printable.ts b/src/labels/Printable.ts
index 5185137..3a0f39c 100644
--- a/src/labels/Printable.ts
+++ b/src/labels/Printable.ts
@@ -3,7 +3,8 @@ import CommandGenerator from "@/commands/CommandGenerator";
import { Printer } from "@/printers";
export type PrintConfig = {
- dpi: number
+ dpi: number,
+ textWidth: (text: string, font: string, fontSize: number) => number
}
/**
diff --git a/src/labels/fields/Text.ts b/src/labels/fields/Text.ts
index 1b924df..b0df90a 100644
--- a/src/labels/fields/Text.ts
+++ b/src/labels/fields/Text.ts
@@ -2,8 +2,22 @@ import { Command, PrinterLanguage } from "@/commands"
import LabelField from "./LabelField"
import { PrintConfig } from "../Printable"
import { dotToPoint } from "@/helpers/UnitUtils"
+import CommandGenerator from "@/commands/CommandGenerator"
+import { isWhitespace } from "@/helpers/StringUtils"
+import { NodeType, parse, HTMLElement, Node } from "node-html-parser"
export type TextFieldType = "singleline"|"multiline"
+type Context = {
+ language: PrinterLanguage,
+ generator: CommandGenerator,
+ config?: PrintConfig
+}
+
+type TextDecoration = "underline"|"strike"
+
+const BOLD_TAG = "b"
+const UNDERLINE_TAG = "u"
+const STRIKE_TAG = "s"
/**
* Presents a piece of text on the label
@@ -25,6 +39,9 @@ export default class Text extends LabelField {
private fontSize: number = 10
private font: string = "default"
private type: TextFieldType = "singleline"
+ private context: Context|undefined = undefined
+ private readonly lineSpacing = 1
+
/**
* Width of the text.
* If set, the text will be clipped to this size
@@ -66,7 +83,7 @@ export default class Text extends LabelField {
}
/**
- * Set a font to use.
+ * Set a font to use as a base. If no formatting is set on the text with a html tag, this will be used
* Note: The font name either has to be a built in font on your printer or a font
* that is registered on the label using 'registerFont'.
*
@@ -78,8 +95,239 @@ export default class Text extends LabelField {
this.fontSize = size
}
- commandForLanguage(language: PrinterLanguage, config?: PrintConfig): Promise {
- const fontSize = dotToPoint(this.fontSize, config?.dpi ?? 203)
- return this.commandGeneratorFor(language).text(this.content, this.x, this.y, this.font, fontSize)
+ async commandForLanguage(language: PrinterLanguage, config?: PrintConfig): Promise {
+ this.context = {
+ config,
+ language,
+ generator: this.commandGeneratorFor(language)
+ }
+
+
+ let command: Command
+ if(this.formatted) {
+ command = this.generateFormattedText()
+ } else {
+ command = this.generatePlainText()
+ }
+ this.context = undefined
+
+ return command
+ }
+
+ private generateFormattedText(): Command {
+ if(!this.context) throw "context-not-set"
+
+ // Starter values
+ const rootNode = parse(this.content)
+ const { command } = this.generateFormattedRecursive(this.x, this.y, rootNode, this.font, this.fontSize, [])
+ return command
+ }
+
+ /**
+ * Iterats the nodes in a html text and generates text commands for it
+ */
+ private generateFormattedRecursive(initialX: number, initialY: number, rootNode: Node, font: string, fontSize: number, features: TextDecoration[]): {x: number, y: number, command: Command} {
+ if(rootNode.nodeType == NodeType.TEXT_NODE) {
+ const result = this.generatePlainTextCore(rootNode.innerText, initialX, initialY, font, fontSize, features)
+ return result
+ } else {
+ const elementNode = rootNode as HTMLElement
+ const tag = elementNode.rawTagName
+
+ let commands: Command[] = []
+ let currentX = initialX
+ let currentY = initialY
+
+ const baseFont = tag == BOLD_TAG ? this.getBoldFont(font) : font
+ const baseFontSize = fontSize
+ let baseFeatures = [...features]
+
+ if(tag == UNDERLINE_TAG) {
+ baseFeatures.push("underline")
+ } else if(tag == STRIKE_TAG) {
+ baseFeatures.push("strike")
+ }
+
+ elementNode.childNodes.forEach(node => {
+ const {x,y,command} = this.generateFormattedRecursive(currentX, currentY, node, baseFont, baseFontSize, baseFeatures, )
+ currentX = x
+ currentY = y
+ commands.push(command)
+ })
+
+ return {
+ x: currentX,
+ y: currentY,
+ command: this.context!.generator.commandGroup(commands)
+ }
+ }
+ }
+
+ /**
+ * Generate commands for plain text
+ * @param config
+ * @returns
+ */
+ private generatePlainText(): Command {
+ const {command} = this.generatePlainTextCore(this.content, this.x, this.y, this.font, this.fontSize)
+ return command
+ }
+
+ /**
+ * Generate commands for plain text
+ * @param config
+ * @returns
+ */
+ private generatePlainTextCore(content: string, initialX: number, initialY: number, font: string, fontSize: number, features: TextDecoration[] = []): {x: number, y: number, command: Command} {
+ if(!this.context) throw "context-not-set"
+
+ const textWidhtFunction = (this.context.config?.textWidth ?? this.defaultTextWidth)
+ const fullWidth = textWidhtFunction(content, font, fontSize)
+
+ if(this.width) {
+ const initialPadding = initialX - this.x
+ // Because we may start from further in the row, the first rows width may be smaller
+ let rowWidth = this.width - initialPadding
+
+ // We may not start from the begining of the textbox so we have to offset
+ // by our current position
+ if(fullWidth <= rowWidth) {
+ return {
+ x: initialX + fullWidth,
+ y: initialY,
+ command: this.textCommand(content, initialX, initialY, font, fontSize)
+ }
+ } else {
+ const commands: Command[] = []
+
+ let x = initialX
+ let y = initialY
+ let remainingContent = content
+ let remainingWidth = fullWidth
+ let currentHeight = 0
+
+ let finalX = x
+ let finalY = y
+
+ do {
+ // This will be the last row of the text.
+ if(remainingWidth < rowWidth) {
+ finalX = this.x + remainingWidth
+ finalY = y
+ commands.push(this.textCommand(remainingContent, x, y, font, fontSize))
+ remainingContent = ""
+ } else {
+ // On how many rows this text would fit
+ let rows = remainingWidth / rowWidth
+ // From the second row, all rows are full width
+ rowWidth = this.width
+ // Which caracter is the last if dividing into the right number of rows
+ let rowEndIndex = Math.floor(remainingContent.length / rows)
+ let originalRowEndIndex = rowEndIndex
+
+ // This means we have to fit a relatively short text into
+ // a lot of rows which can only happen if the row width is very small
+ // in this case, we have to go to a new line
+ if(rowEndIndex == 0) {
+ x = this.x
+ y += fontSize + this.lineSpacing
+ continue
+ }
+
+ // Scenario 1: Current index is in a middle of row
+ // I am iron m@n
+ // End this row with the last words last character
+
+ // Scneraio 2: Current index is space:
+ // I am iron@man
+ // No action, but to simplify code, we threat as scenario 1
+
+ // Scenario 3: Current index is right before a sapce
+ // I am iro@ man
+ // Start next row from the first latter
+
+ // Find the end of the last word
+ while(
+ ! (
+ !isWhitespace(remainingContent.charAt(rowEndIndex)) &&
+ (
+ rowEndIndex == remainingContent.length - 1 ||
+ isWhitespace(remainingContent.charAt(rowEndIndex + 1))
+ )
+ ) && rowEndIndex > 0
+ ) { rowEndIndex -- }
+
+ let nextRowStartIndex = rowEndIndex + 1
+ // We didn't find a space, we split the text wherever we land
+ if(rowEndIndex == 0) {
+ rowEndIndex = originalRowEndIndex
+ nextRowStartIndex = originalRowEndIndex + 1
+ } else {
+ while(
+ isWhitespace(remainingContent.charAt(nextRowStartIndex)) &&
+ nextRowStartIndex < remainingContent.length
+ ) { nextRowStartIndex ++ }
+ }
+
+ const thisRow = remainingContent.substring(0, rowEndIndex + 1)
+ commands.push(this.textCommand(thisRow, x, y, font, fontSize))
+
+ // Make sure to move the cursor back to the left side of the text box
+ // as we may have started further into the row
+ x = this.x
+ y += fontSize + this.lineSpacing
+ currentHeight = y - this.y
+ remainingContent = remainingContent.substring(nextRowStartIndex)
+ remainingWidth = textWidhtFunction(remainingContent, font, fontSize)
+ }
+ } while(
+ // We don't have a height constraint or we are still within bounds
+ // and there is still content
+ // and we are supporting multiline
+ (this.height == undefined || (currentHeight + fontSize) <= this.height) &&
+ (remainingContent != "") &&
+ this.type == "multiline"
+ )
+
+ return {
+ x: finalX,
+ y: finalY,
+ command: this.context!.generator.commandGroup(commands)
+ }
+ }
+ } else {
+ //
+ return {
+ x: initialX + fullWidth,
+ y: initialY,
+ command: this.textCommand(content, initialX, initialY, font, fontSize)
+ }
+ }
+ }
+
+ private textCommand(text: string, x: number, y: number, font: string, fontSize: number) {
+ const finalFontSize = dotToPoint(fontSize, this.context!.config?.dpi ?? 203)
+ const finalFont = this.getFontName(font)
+ return this.context!.generator.text(text, x, y, finalFont, finalFontSize)
+ }
+
+ /**
+ * This function is used to calculate the font size if no
+ * print config is provided. This will asume that the font has square characters
+ */
+ private defaultTextWidth(text: string, _f: string, fontSize: number) {
+ return text.length * fontSize
+ }
+
+ private getBoldFont(font: string) {
+ if(font.includes('-bold')) {
+ return font
+ } else {
+ return `${font}-bold`
+ }
+ }
+
+ private getFontName(font: string) {
+ return `${font}.TTF`
}
}
\ No newline at end of file
diff --git a/src/labels/types.ts b/src/labels/types.ts
index 91dda00..848d318 100644
--- a/src/labels/types.ts
+++ b/src/labels/types.ts
@@ -1,6 +1,7 @@
+import { Font as FKFont } from "fontkit"
+
export type Font = {
name: string,
data: ArrayBufferLike
- // width: number,
- // height: number
+ font: FKFont
}
\ No newline at end of file