-
Notifications
You must be signed in to change notification settings - Fork 0
/
fittext.coffee
156 lines (122 loc) · 3.56 KB
/
fittext.coffee
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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
timer = ->
previous = new Date()
(note) ->
now = new Date()
elapsed = now-previous
#console.log "#{note} [#{elapsed}ms]"
previous = now
$.fn.fitText = (method) ->
if method == 'destroy'
this.each ->
$el = $ this
unfit $el
return this
if method == 'clear'
this.each ->
$el = $ this
#clear $el
unfit $el
return this
this.each -> fit $(this)
this
unfit = ($el) ->
#id = $el.attr 'id'
#return unless id
clear $el
$el.removeAttr 'style'
clear = ($el) ->
$el.data 'fitTextCache', {}
fit = ($el) ->
# remove the display: inline-block hack again
cleanup = ->
$el.css 'display': 'block'
id = $el.attr 'id'
#return unless id
# skip unless there's text
text = $el.text().trim()
return unless text
log = timer()
# add the cache if it doesn't exist
clear($el) unless $el.data('fitTextCache')
# read the cached sizes
cache = $el.data('fitTextCache')
# try and fit the parent's width
$parent = $el.parent()
pw = $parent.width()
# get font size/line-height ratio so we can keep it later
fontSize = parseInt($el.css('font-size'), 10)
lineHeight = parseInt($el.css('line-height'), 10)
lineHeightRatio = lineHeight/fontSize
#console.log lineHeight, fontSize, lineHeightRatio
#lineHeightRatio = 1
# set the font size and line height while trying to maintain the font
# size/line height ratio and return the new width
total = 0
setSize = (fs, skipMeasure) ->
total += 1
lh = Math.floor fs*lineHeightRatio
$el.css
'display': 'inline-block'
'white-space': 'nowrap'
'font-size': "#{fs}px"
'line-height': "#{lh}px"
'min-height': "#{lh}px" # hack for contenteditables
if skipMeasure
null
else
width = $el.outerWidth(true)
width
# if we already have a cached size for this width, use that and exit
if cache[pw]
# TODO: I don't think we clean up properly here
setSize cache[pw], true
log "using cached for #{id}:#{pw} (#{cache[pw]})"
return cleanup()
# start off at the minimum size
w = setSize 10
# don't bother if the minimum size is already too big
if w > pw
log("too big for #{id}")
return cleanup()
minSize = 10
maxSize = 1000
# heuristics..
guess = Math.floor(pw/w*10)
minSize = Math.max minSize, guess-20
maxSize = Math.min maxSize, guess+20
# find the closest font size where the width is <= the parent width
[newFontSize, acc] = search(pw, setSize, minSize, maxSize, minSize)
unless acc == 'exact'
# we didn't get an exact match, so use what we found
w = setSize newFontSize
# cache this value
cache[pw] = newFontSize
$el.data('fitTextCache', cache)
log("#{total} calls for #{id}")
cleanup()
###
find the closest input applied with function f (where inputs fall in the range
min..max) where the output is equal to or smaller than target. It is assumed
that bigger inputs mean bigger outputs.
so output = f(value) where output <= target
Or in less general terms:
* target == parent width
* f == "set the font size and measure the resultant width"
* min == 10px font size
* max == 1000px font size
* closest == the last size tried where the text's width was
smaller than the parent width
###
search = (target, f, min, max, closest) ->
diff = max-min
if diff <= 0
# the last thing that was too small is the closest match
return [closest, 'closest']
mid = min + Math.floor(diff/2)
val = f mid
if val == target
[mid, 'exact']
else if val < target
search(target, f, mid+1, max, mid)
else
search(target, f, min, mid-1, closest)