From: MTRNord Date: Sun, 12 Jan 2025 14:42:07 +0000 (+0100) Subject: Calculate the correct text height and correctly scale the font sizes to ensure text... X-Git-Url: https://gerrit.midnightthoughts.space/gitweb?a=commitdiff_plain;h=55325c46657833c6e51786bf4b48c13949239011;p=neoboard-miro-converter.git Calculate the correct text height and correctly scale the font sizes to ensure text displaying works Change-Id: I8b832a1aab2d6b3dd6db9434608df61fb4d0fb74 --- diff --git a/package.json b/package.json index 6a9eacc..e9a425a 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "react-dom": "19.0.0", "sass": "^1.83.1", "string-strip-html": "^13.4.8", - "zod": "^3.24.1" + "zod": "^3.24.1", + "canvas": "^3.0.1" }, "devDependencies": { "@mirohq/websdk-types": "latest", @@ -25,7 +26,7 @@ "@types/node": "^18.19.70", "@types/react": "19.0.4", "@types/react-dom": "19.0.2", - "eslint": "^9", + "eslint": "^9.18.0", "eslint-config-next": "15.1.4", "typescript": "4.9.5" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 646ebe1..3ac7b58 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@mirohq/miro-api': specifier: ^2.2.2 version: 2.2.2 + canvas: + specifier: ^3.0.1 + version: 3.0.1 cookie: specifier: ^0.5.0 version: 0.5.0 @@ -41,7 +44,7 @@ importers: devDependencies: '@mirohq/websdk-types': specifier: latest - version: 2.16.1(@typescript-eslint/eslint-plugin@8.19.1(@typescript-eslint/parser@8.19.1(eslint@9.18.0)(typescript@4.9.5))(eslint@9.18.0)(typescript@4.9.5))(@typescript-eslint/parser@8.19.1(eslint@9.18.0)(typescript@4.9.5))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.19.1(eslint@9.18.0)(typescript@4.9.5))(eslint-import-resolver-typescript@3.7.0)(eslint@9.18.0))(eslint@9.18.0) + version: 2.16.1(@typescript-eslint/eslint-plugin@8.19.1(@typescript-eslint/parser@8.19.1(eslint@9.18.0)(typescript@4.9.5))(eslint@9.18.0)(typescript@4.9.5))(@typescript-eslint/parser@8.19.1(eslint@9.18.0)(typescript@4.9.5))(eslint-plugin-import@2.31.0)(eslint@9.18.0) '@types/cookie': specifier: ^0.5.4 version: 0.5.4 @@ -55,7 +58,7 @@ importers: specifier: 19.0.2 version: 19.0.2(@types/react@19.0.4) eslint: - specifier: ^9 + specifier: ^9.18.0 version: 9.18.0 eslint-config-next: specifier: 15.1.4 @@ -583,6 +586,12 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -593,6 +602,9 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} @@ -616,6 +628,10 @@ packages: caniuse-lite@1.0.30001692: resolution: {integrity: sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A==} + canvas@3.0.1: + resolution: {integrity: sha512-PcpVF4f8RubAeN/jCQQ/UymDKzOiLmRPph8fOTzDnlsUihkO/AUlxuhaa7wGRc3vMcCbV1fzuvyu5cWZlIcn1w==} + engines: {node: ^18.12.0 || >= 20.9.0} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -624,6 +640,9 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} @@ -712,6 +731,18 @@ packages: supports-color: optional: true + decompress-response@4.2.1: + resolution: {integrity: sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==} + engines: {node: '>=8'} + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -751,6 +782,9 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + enhanced-resolve@5.18.0: resolution: {integrity: sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==} engines: {node: '>=10.13.0'} @@ -906,6 +940,10 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -952,6 +990,9 @@ packages: resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} engines: {node: '>= 6'} + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -977,6 +1018,9 @@ packages: get-tsconfig@4.8.1: resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1036,6 +1080,9 @@ packages: html-entities@2.5.2: resolution: {integrity: sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==} + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1054,6 +1101,9 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} @@ -1248,6 +1298,14 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mimic-response@2.1.0: + resolution: {integrity: sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==} + engines: {node: '>=8'} + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -1261,6 +1319,9 @@ packages: mirotone@5.3.0: resolution: {integrity: sha512-PsAMyy48OwGleHWtOCGscjcqt2U5tFbFvTKdUeamv9+bGvt7lQHHkpccQLCqdKfMJeAIySRAJggMDmm7fls+kQ==} + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1269,6 +1330,9 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + napi-build-utils@1.0.2: + resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -1293,6 +1357,10 @@ packages: sass: optional: true + node-abi@3.71.0: + resolution: {integrity: sha512-SZ40vRiy/+wRTf21hxkkEjPJZpARzUMVcJoQse2EF8qkUWbbO2z7vd5oA/H6bVH6SZQ5STGcu0KRDS7biNRfxw==} + engines: {node: '>=10'} + node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} @@ -1340,6 +1408,9 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1386,6 +1457,11 @@ packages: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} + prebuild-install@7.1.2: + resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} + engines: {node: '>=10'} + hasBin: true + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -1396,6 +1472,9 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + pump@3.0.2: + resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -1419,6 +1498,10 @@ packages: resolution: {integrity: sha512-fhNEG0vGi7bESitNNqNBAfYPdl2efB+1paFlI8BQDCNkruERKuuhG8LkQClDIVqUJLkrmKuOSPQ3xZHqVnVo3Q==} engines: {node: '>=14.18.0'} + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + react-dom@19.0.0: resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==} peerDependencies: @@ -1437,9 +1520,13 @@ packages: readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} - readdirp@4.0.2: - resolution: {integrity: sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==} - engines: {node: '>= 14.16.0'} + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@4.1.1: + resolution: {integrity: sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==} + engines: {node: '>= 14.18.0'} reflect.getprototypeof@1.0.10: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} @@ -1554,6 +1641,15 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@3.1.1: + resolution: {integrity: sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} @@ -1617,6 +1713,10 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -1646,6 +1746,13 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} + tar-fs@2.1.2: + resolution: {integrity: sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + through2@0.4.2: resolution: {integrity: sha512-45Llu+EwHKtAZYTPPVn3XZHBgakWMN3rokhEv5hu596XP+cNgplMg+Gj+1nmAvj+L0K7+N49zBKx5rah5u0QIQ==} @@ -1671,6 +1778,9 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -1744,6 +1854,9 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + xtend@2.1.2: resolution: {integrity: sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ==} engines: {node: '>=0.4'} @@ -1901,7 +2014,7 @@ snapshots: transitivePeerDependencies: - encoding - '@mirohq/websdk-types@2.16.1(@typescript-eslint/eslint-plugin@8.19.1(@typescript-eslint/parser@8.19.1(eslint@9.18.0)(typescript@4.9.5))(eslint@9.18.0)(typescript@4.9.5))(@typescript-eslint/parser@8.19.1(eslint@9.18.0)(typescript@4.9.5))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.19.1(eslint@9.18.0)(typescript@4.9.5))(eslint-import-resolver-typescript@3.7.0)(eslint@9.18.0))(eslint@9.18.0)': + '@mirohq/websdk-types@2.16.1(@typescript-eslint/eslint-plugin@8.19.1(@typescript-eslint/parser@8.19.1(eslint@9.18.0)(typescript@4.9.5))(eslint@9.18.0)(typescript@4.9.5))(@typescript-eslint/parser@8.19.1(eslint@9.18.0)(typescript@4.9.5))(eslint-plugin-import@2.31.0)(eslint@9.18.0)': dependencies: typescript: 4.9.5 optionalDependencies: @@ -2227,6 +2340,14 @@ snapshots: balanced-match@1.0.2: {} + base64-js@1.5.1: {} + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -2240,6 +2361,11 @@ snapshots: dependencies: fill-range: 7.1.1 + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + busboy@1.6.0: dependencies: streamsearch: 1.1.0 @@ -2265,6 +2391,12 @@ snapshots: caniuse-lite@1.0.30001692: {} + canvas@3.0.1: + dependencies: + node-addon-api: 7.1.1 + prebuild-install: 7.1.2 + simple-get: 3.1.1 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -2272,7 +2404,9 @@ snapshots: chokidar@4.0.3: dependencies: - readdirp: 4.0.2 + readdirp: 4.1.1 + + chownr@1.1.4: {} client-only@0.0.1: {} @@ -2356,6 +2490,16 @@ snapshots: dependencies: ms: 2.1.3 + decompress-response@4.2.1: + dependencies: + mimic-response: 2.1.0 + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + + deep-extend@0.6.0: {} + deep-is@0.1.4: {} define-data-property@1.1.4: @@ -2375,8 +2519,7 @@ snapshots: detect-libc@1.0.3: optional: true - detect-libc@2.0.3: - optional: true + detect-libc@2.0.3: {} doctrine@2.1.0: dependencies: @@ -2392,6 +2535,10 @@ snapshots: emoji-regex@9.2.2: {} + end-of-stream@1.4.4: + dependencies: + once: 1.4.0 + enhanced-resolve@5.18.0: dependencies: graceful-fs: 4.2.11 @@ -2692,6 +2839,8 @@ snapshots: esutils@2.0.3: {} + expand-template@2.0.3: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.1: @@ -2748,6 +2897,8 @@ snapshots: combined-stream: 1.0.8 mime-types: 2.1.35 + fs-constants@1.0.0: {} + function-bind@1.1.2: {} function.prototype.name@1.1.8: @@ -2789,6 +2940,8 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + github-from-package@0.0.0: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -2839,6 +2992,8 @@ snapshots: html-entities@2.5.2: {} + ieee754@1.2.1: {} + ignore@5.3.2: {} immutable@5.0.3: {} @@ -2852,6 +3007,8 @@ snapshots: inherits@2.0.4: {} + ini@1.3.8: {} + internal-slot@1.1.0: dependencies: es-errors: 1.3.0 @@ -3051,6 +3208,10 @@ snapshots: dependencies: mime-db: 1.52.0 + mimic-response@2.1.0: {} + + mimic-response@3.1.0: {} + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 @@ -3066,10 +3227,14 @@ snapshots: '@mirohq/design-tokens': 5.1.1 gulp-file: 0.4.0 + mkdirp-classic@0.5.3: {} + ms@2.1.3: {} nanoid@3.3.8: {} + napi-build-utils@1.0.2: {} + natural-compare@1.4.0: {} next@15.1.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.83.1): @@ -3098,8 +3263,11 @@ snapshots: - '@babel/core' - babel-plugin-macros - node-addon-api@7.1.1: - optional: true + node-abi@3.71.0: + dependencies: + semver: 7.6.3 + + node-addon-api@7.1.1: {} node-fetch@2.7.0: dependencies: @@ -3148,6 +3316,10 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.0.0 + once@1.4.0: + dependencies: + wrappy: 1.0.2 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -3193,6 +3365,21 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + prebuild-install@7.1.2: + dependencies: + detect-libc: 2.0.3 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 1.0.2 + node-abi: 3.71.0 + pump: 3.0.2 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.2 + tunnel-agent: 0.6.0 + prelude-ls@1.2.1: {} process-nextick-args@2.0.1: {} @@ -3203,6 +3390,11 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + pump@3.0.2: + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + punycode@2.3.1: {} queue-microtask@1.2.3: {} @@ -3226,6 +3418,13 @@ snapshots: ranges-sort@6.0.11: {} + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + react-dom@19.0.0(react@19.0.0): dependencies: react: 19.0.0 @@ -3252,7 +3451,13 @@ snapshots: string_decoder: 1.1.1 util-deprecate: 1.0.2 - readdirp@4.0.2: {} + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readdirp@4.1.1: {} reflect.getprototypeof@1.0.10: dependencies: @@ -3420,6 +3625,20 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + simple-concat@1.0.1: {} + + simple-get@3.1.1: + dependencies: + decompress-response: 4.2.1 + once: 1.4.0 + simple-concat: 1.0.1 + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + simple-swizzle@0.2.2: dependencies: is-arrayish: 0.3.2 @@ -3508,6 +3727,8 @@ snapshots: strip-bom@3.0.0: {} + strip-json-comments@2.0.1: {} + strip-json-comments@3.1.1: {} styled-jsx@5.1.6(react@19.0.0): @@ -3523,6 +3744,21 @@ snapshots: tapable@2.2.1: {} + tar-fs@2.1.2: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.2 + tar-stream: 2.2.0 + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.4 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + through2@0.4.2: dependencies: readable-stream: 1.0.34 @@ -3549,6 +3785,10 @@ snapshots: tslib@2.8.1: {} + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.1.2 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -3665,6 +3905,8 @@ snapshots: word-wrap@1.2.5: {} + wrappy@1.0.2: {} + xtend@2.1.2: dependencies: object-keys: 0.4.0 diff --git a/src/app/importActions.ts b/src/app/importActions.ts index 0fbcbfc..9d14df7 100644 --- a/src/app/importActions.ts +++ b/src/app/importActions.ts @@ -5,6 +5,7 @@ import initMiroAPI from '../utils/initMiroAPI'; import { Path, Shape, Slide, Whiteboard, Image, neoboardWhiteboardWidth, neoboardWhiteboardHeight } from './neoboardTypes'; import { stripHtml } from 'string-strip-html'; import { GenericItemCursorPaged } from '@mirohq/miro-api/dist/api'; +import { createCanvas } from 'canvas'; export type FormState = { neoboards: { @@ -165,8 +166,6 @@ export async function importBoard(prevState: FormState, formData: FormData): Pro } ]; - console.log('points', points); - const path: Path = { type: 'path', @@ -238,7 +237,8 @@ function fitItemsBestIntoFrame(board: Whiteboard): Whiteboard { let maxY = Number.MIN_VALUE; for (const item of slide.elements) { - if (item.type === 'shape' || item.type === 'image') { + // @ts-expect-error - Internal type for scaling used for text shapes + if (item.type === 'shape' || item.type === 'image' || item.type === 'textshape') { const shape = item as Shape; // Make sure that we deal with x and y being in the center of the shape and not the corner. // This means the bounds of the shape itself are x - width / 2, y - height / 2, x + width / 2, y + height / 2 @@ -269,6 +269,14 @@ function fitItemsBestIntoFrame(board: Whiteboard): Whiteboard { for (const item of slide.elements) { if (item.type === 'shape' || item.type === 'image') { const shape = item; + + // Scale the font size as well + if (shape.type === 'shape') { + if (shape.textSize) { + shape.textSize = shape.textSize * scale; + } + } + // Ensure we account for the fact that our coordinates are in the center of the shape currently but we require them to be at the top left // This means we need to substract half of the width from the x and substract half of the height to the y const transformedShape = { @@ -295,6 +303,30 @@ function fitItemsBestIntoFrame(board: Whiteboard): Whiteboard { }; transformedSlide.elements.push(transformedPath); + // @ts-expect-error - Internal type for scaling used for text shapes + } else if (item.type === 'textshape') { + const shape = item as Shape; + if (shape.textSize) { + shape.textSize = shape.textSize * scale; + } + + const newWidth = shape.width * scale; + + // Recalculate the bounding box of the text as the font and width are changed + const textBounds = calculateTextBoundingBox(shape.text, shape.textSize ?? 16, "Inter", newWidth); + + const transformedShape = { + ...shape, + type: 'shape' as const, + position: { + x: (shape.position.x - minX - shape.width / 2) * scale + neoboardPadding, + y: (shape.position.y - minY - shape.height / 2) * scale + neoboardPadding, + }, + width: textBounds.width, + height: textBounds.height, + }; + + transformedSlide.elements.push(transformedShape); } } @@ -305,7 +337,7 @@ function fitItemsBestIntoFrame(board: Whiteboard): Whiteboard { } function hexToRGB(hex: string, alpha?: number): string { - var r = parseInt(hex.slice(1, 3), 16), + const r = parseInt(hex.slice(1, 3), 16), g = parseInt(hex.slice(3, 5), 16), b = parseInt(hex.slice(5, 7), 16); @@ -351,6 +383,7 @@ function convertShape(item: ShapeItem): Shape | undefined { text: stripHtml(item.data?.content ?? "").result, textAlignment: (item.style?.textAlign ?? "left") as "left" | "center" | "right", textColor: item.style?.color, + textSize: parseInt(item.style?.fontSize ?? "16"), // TODO: Revisit this when neoboard can do html or inline styles textBold: false, textItalic: false, @@ -412,23 +445,72 @@ async function convertImage(item: ImageItem): Promise<{ }; } +// Calculate the bounding box of the text where only font size and one side of the box is known. +// We expect to see multiline text here. +// Use canvas to calculate the bounding box of the text +// We use node-canvas as this is a server-side function +function calculateTextBoundingBox(text: string, fontSize: number, fontFamily: string, width: number): { width: number, height: number } { + const canvas = createCanvas(1, 1); + const context = canvas.getContext('2d'); + if (!context) { + throw new Error('Failed to get 2D context'); + } + + const neoboardPadding = 10; + const maxWidth = width - neoboardPadding * 2; + + context.font = `${fontSize}px ${fontFamily}`; + const words = text.split(' '); + let line = ''; + let totalHeight = 0; + const lineHeight = fontSize * 1.2; // Assuming line height is 1.2 times the font size + + for (let n = 0; n < words.length; n++) { + const testLine = line + words[n] + ' '; + const metrics = context.measureText(testLine); + const testWidth = metrics.width; + if (testWidth > maxWidth && n > 0) { + totalHeight += lineHeight; + line = words[n] + ' '; + } else { + line = testLine; + } + } + totalHeight += lineHeight; // Add height for the last line + // Add padding to the height + totalHeight += neoboardPadding * 2; + + return { width, height: totalHeight }; +} + function convertText(item: TextItem): Shape { + const text = stripHtml(item.data?.content ?? "").result; + const textBounds = calculateTextBoundingBox(text, parseInt(item.style?.fontSize ?? "16"), "Inter", item.geometry?.width ?? 16); + + const neoboardPadding = 5; + + const fillOpacity = parseFloat(item.style?.fillOpacity ?? "1.0"); + const fillColor = fillOpacity > 0 ? hexToRGB(mapColorNameToHex(item.style?.fillColor ?? '#ffffff'), fillOpacity) : "transparent"; + return { - type: 'shape', + // @ts-expect-error - Internal type for scaling + type: 'textshape', kind: 'rectangle', position: { x: item.position?.x ?? 0, y: item.position?.y ?? 0, }, - width: item.geometry?.width ?? 1, - height: item.geometry?.height ?? 1, - fillColor: "transparent", + width: textBounds.width + neoboardPadding, + height: textBounds.height + neoboardPadding, + fillColor: fillColor, + fillOpacity: fillOpacity, strokeColor: "transparent", strokeWidth: 0, borderRadius: 0, - text: stripHtml(item.data?.content ?? "").result, + text: text, textAlignment: (item.style?.textAlign ?? "left") as "left" | "center" | "right", textColor: item.style?.color, + textSize: parseInt(item.style?.fontSize ?? "16"), // TODO: Revisit this when neoboard can do html or inline styles textBold: false, textItalic: false, diff --git a/src/app/neoboardTypes.ts b/src/app/neoboardTypes.ts index 1639ca9..ef065f1 100644 --- a/src/app/neoboardTypes.ts +++ b/src/app/neoboardTypes.ts @@ -20,6 +20,7 @@ export const zShape = z.object({ textColor: z.string().optional(), textBold: z.boolean().optional(), textItalic: z.boolean().optional(), + textSize: z.number().min(0).optional(), }); export type Shape = z.infer;