Multi-Line Padded Text with Canvas
After reading "Multi-Line Padded Text" on CSS Tricks I decided to try making a version which used border-radius. Unfortunately, I found there was no easy way of doing this with CSS. I experimented for a while but ultimately tried implementing a solution in canvas. This is that.
HTML
<h1>Multi-Line Padded Text with Border-radius</h1>
<p>By <a href="http://adamschwartz.co">Adam Schwartz</a></p>
<p>Resize the browser and watch the text wrap.</p>
<h2>Canvas solution (supports border-radius)</h2>
<div id="wrap">
<canvas id="canvas"></canvas>
</div>
<h2>CSS solution (uses <code>box-decoration-break</code>)</h2>
<div id="css-wrap">
<div id="css"></div>
</div>
CSS
body {
-webkit-font-smoothing: antialiased;
font-family: 'Helvetica Neue', sans-serif;
font-size: 16px;
max-width: 46em;
padding: 1em;
margin-left: auto;
margin-right: auto
}
a, a:link, a:visited {
color: inherit;
text-decoration: none;
border-bottom: 1px solid #000;
}
@media (min-width: 30em) {
body {
padding: 2em
}
}
@media (min-width: 56em) {
html {
display: table;
margin-left: auto;
margin-right: auto;
min-height: 100%
}
body {
display: table-cell;
vertical-align: middle;
padding-bottom: 6em;
}
}
h1 {
letter-spacing: .01em;
margin-top: 0;
font-size: 1.333em;
margin-bottom: 1em;
}
h2 {
letter-spacing: .01em;
font-weight: 500;
margin-top: 0;
font-size: 1.125em;
margin: 1em 0;
}
p {
letter-spacing: .01em;
line-height: 1.5em
}
#wrap {
height: 0;
max-width: 100%;
overflow: hidden
}
#canvas {
font-size: 1em
}
#css-wrap {
padding-right: 1em
}
#css {
display: inline;
box-sizing: border-box;
background-color: #ddedff;
color: #303786;
padding: .5em .625em;
line-height: 1.8em;
border-radius: .1875em;
-webkit-box-decoration-break: clone
}
CoffeeScript
context = canvas.getContext '2d'
CanvasRenderingContext2D::roundRect = (x, y, w, h, r, a = [1, 1, 1, 1]) ->
arc = (i) -> if a[i] then 'arcTo' else 'lineTo'
r = w / 2 if w < 2 * r
r = h / 2 if h < 2 * r
@beginPath()
@moveTo x+r, y
@[arc 1] x+w, y, x+w, y+h, r
@[arc 2] x+w, y+h, x, y+h, r
@[arc 3] x, y+h, x, y, r
@[arc 0] x, y, x+w, y, r
@closePath()
@
getLines = (context, text, maxWidth) ->
words = text.split ' '
lines = []
currentLine = words[0]
for word, i in words
if i > 0
width = context.measureText("#{ currentLine } #{ word }").width
if width < maxWidth
currentLine += " #{ word }"
else
lines.push currentLine
currentLine = word
lines.push currentLine
lines
text = 'بتنی خیبخیهبخیهبخ هبخح حخسهبحس یبحسب ب بسح به سب س یب سبس یب خسهح هحخه ح خهث خقث سخحث ق سثق سثق س قثس ثقثق ث ق ثقصحخهقحص خث قص ثق ق ق صثق ص ثق صثق صثقهصحثخقهحخ خح هحخص ثه قخص ثقص ق'
css.innerHTML = text
canvas.width = 3000
canvas.height = 1000
fontSize = parseInt getComputedStyle(canvas)['font-size'], 10
lineHeight = parseInt getComputedStyle(canvas)['line-height'], 10
background = '#ddedff'
color = '#303786'
lineHeight = fontSize * 1.5 if isNaN lineHeight
context.font = fontSize + "px 'Helvetica Neue', sans-serif"
bubbleSidePadding = parseInt(fontSize * .625, 10)
bubbleBetweenPadding = parseInt(fontSize * .1875, 10)
borderRadius = fontSize * .1875
bubbleTop = (lineNumber) -> lineNumber * (lineHeight + bubbleBetweenPadding)
draw = ->
lines = getLines context, text, wrap.clientWidth - (2 * bubbleSidePadding)
wrap.style.height = bubbleTop(lines.length) + 4 * bubbleBetweenPadding + 'px'
context.font = fontSize + "px 'Helvetica Neue', sans-serif"
for line, i in lines
width = context.measureText(line).width
context.fillStyle = background
arc = [1, 1, 1, 1]
...