File size: 4,044 Bytes
181bd77
 
 
 
 
 
 
 
 
a522ff9
c82338a
181bd77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c82338a
181bd77
c82338a
181bd77
c82338a
 
181bd77
 
 
 
 
 
 
 
 
c82338a
a522ff9
c82338a
 
 
 
 
 
 
 
 
 
 
 
075181d
c82338a
075181d
c82338a
075181d
c82338a
075181d
c82338a
075181d
c82338a
 
181bd77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c82338a
181bd77
 
 
 
 
c82338a
a88f8ce
181bd77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/**
  * Uses canvas.measureText to compute and return the width of the given text of given font in pixels.
  * 
  * @param {String} text The text to be rendered.
  * @param {String} font The css font descriptor that text is to be rendered with (e.g. "bold 14px verdana").
  * 
  * @see https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393
  */

import { useTimeline } from ".."

function getTextWidthInCanvas(text: string, font: string) {
  if (typeof window === "undefined") {
    return 0
  }
  const canvas = document.createElement("canvas")
  const context = canvas.getContext("2d")
  if (!context) { return 0 }

  context.font = font
  const metrics = context.measureText(text)
  return metrics.width
}


// one option could be to pre-compute some of the width
const characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789()_-"+=,;:?/\\&@#'.split('')
const charLength = characters.reduce((acc, char) => ({
  ...acc,
  [char]: getTextWidthInCanvas(char, "bold Arial")
}), {} as Record<string, number>)
let defaultCharLength = 5.561523437

// change this whenever you modify the font size
const webglFontWidthFactor = 1.7

/**
 * Compute the text of a simple Arial text in a WebGL environmment
 * This actually just do a lookup + sum
 * 
 * @param text 
 * @returns 
 */
export function getWebGLCharWidth(char: string = ""): number {

  const cellWidthInPixels = useTimeline.getState().cellWidth

  let responsiveHack = 1.5

  if (cellWidthInPixels < 16) {
    responsiveHack = 1.20
  } else if (cellWidthInPixels < 20) {
    responsiveHack = 1.10
  } else if (cellWidthInPixels < 24) {
    responsiveHack = 1.05
  } else if (cellWidthInPixels < 28) {
    responsiveHack = 1
  } else if (cellWidthInPixels < 32) {
    responsiveHack = 1.0
  } else if (cellWidthInPixels < 48) {
    responsiveHack = 0.9
  } else if (cellWidthInPixels < 64) {
    responsiveHack = 0.9
  } else if (cellWidthInPixels < 128) {
    responsiveHack = 0.8
  } else {
    responsiveHack = 0.7
  }
  return responsiveHack * webglFontWidthFactor * (charLength[char] || defaultCharLength)
}

/**
 * Compute the text of a simple Arial text in a WebGL environmment
 * This actually just do a lookup + sum
 * 
 * @param text 
 * @returns 
 */
export function getWebGLTextWidth(text: string = ""): number {
  return text.split('').reduce((s, c) => (s + getWebGLCharWidth(c)), 0)
}

/**
 * Clamp a text to a given
 * @param text 
 * @param maxWidthInPixels 
 * @returns 
 */
export function clampWebGLText(
  input: string,
  maxWidthInPixels: number,
  maxNbLines: number
): string[] {
  let buffer = ""
  let width = 0
  let lines: string[] = []

  const text = `${input || ""}`.replace('\n', ' ').trim()
  const characters = text.split('')
  for (const c of characters) {
    width += getWebGLCharWidth(c)
    buffer += c
    if (width >= maxWidthInPixels) {
      if (lines.length >= (maxNbLines - 1)) {
        buffer = buffer.trim() // to avoid writing "and .."
        buffer += ".."
        break
      } else {
        // TODO: we should do something smarter, which is to split the last sentence
        const words = buffer.split(" ")
        const lastWord = (words.at(-1) || "")
        if (lastWord.length) {
          lines.push(words.slice(0, -1).join(" "))
          buffer = lastWord
          width = getWebGLTextWidth(lastWord)
        } else {
           lines.push(buffer)
           buffer = ""
           width = 0
        }
      }
    }
  }

  if (buffer.length) {
    lines.push(buffer)
  }
  return lines
}

export function clampWebGLTextNaive(input: string = "", maxWidthInPixels: number = 0): string {
  // this cutoff is very approximate as we should make it dependent on each character's width
  // a simple heuristic can be to count the uppercase / lower case
  const maxhInCharacter = Math.ceil(maxWidthInPixels / 3.4)

  const text = `${input || ""}`

  return (text.length >= maxhInCharacter)
              ? `${text.slice(0, maxhInCharacter)}..`
              : text
   
}