neft
Version:
Universal Platform
341 lines (281 loc) • 9.98 kB
text/coffeescript
'use strict'
utils = require 'src/utils'
signal = require 'src/signal'
module.exports = (impl) ->
{Item, Image} = impl.Types
implUtils = impl.utils
{round} = Math
# used to render not visible texts
hatchery = impl._hatchery
reloadFontFamilyQueue = []
isFontReady = false
implUtils.onWindowLoad ->
isFontReady = true
while elem = reloadFontFamilyQueue.pop()
elem.style.fontFamily = elem.style.fontFamily
styles = document.createElement 'style'
styles.innerHTML = SHEET
document.body.appendChild styles
return
reloadFontFamily = (family) ->
if ( and ._family is family) or (not and family is 'sans-serif')
.innerElemStyle.fontFamily = .innerElemStyle.fontFamily
return
fontSizes = Object.create null
getTextSizeWidth = do ->
canvas = document.createElement 'canvas'
ctx = canvas.getContext? '2d'
(font, text) ->
fontSize = fontSizes[font] ||= Object.create null
fontSize[text] ||= (
ctx.font = font;
ctx.measureText(text).width or 0.1
)
loadingTextsByFonts = Object.create null
implUtils.onFontLoaded (name) ->
# clear widths
for font, sizes of fontSizes
for char of sizes
sizes[char] = 0
# update texts sizes
if arr = loadingTextsByFonts[name]
for item in arr
updateContent item
if implUtils.loadedFonts[name]
loadingTextsByFonts[name] = null
return
updatePlainTextSize = (item) ->
data = item._impl
{text, fontFamily} = data
if font = item._font
pixelSize = font._pixelSize
letterSpacing = font._letterSpacing
wordSpacing = font._wordSpacing
else
pixelSize = 14
letterSpacing = 0
wordSpacing = 0
if implUtils.loadingFonts[fontFamily]
arr = loadingTextsByFonts[fontFamily] ||= []
arr.push item
fontDef = data.font
x = width = 0
height = lineHeight = pixelSize * item._lineHeight
maxWidth = if data.wrap then item._width else Infinity
wordWidth = charWidth = 0
textLength = text.length
char = ''
for i in [0..textLength] by 1
if i < textLength
char = text[i]
charWidth = getTextSizeWidth fontDef, char
charWidth += letterSpacing
if i is textLength or char is ' ' or char is '-'
if x + wordWidth > maxWidth
height += lineHeight
x = 0
x += wordWidth
if x > width
width = x
if char is ' ' or char is '-'
x += charWidth + wordSpacing
wordWidth = 0
else if char is '\n'
height += lineHeight
x = 0
else
wordWidth += charWidth
data.contentWidth = width
data.contentHeight = height
return
updateHTMLTextSize = (item) ->
data = item._impl
{innerElem, fontFamily} = data
if implUtils.loadingFonts[fontFamily]
arr = loadingTextsByFonts[fontFamily] ||= []
arr.push item
data.contentWidth = innerElem.offsetWidth
data.contentHeight = innerElem.offsetHeight
if innerElem.parentNode is hatchery
implUtils.prependElement data.elem, innerElem
else if data.contentHeight is 0
hatchery.appendChild data.innerElem
updateContent item
return
updateContent = do ->
pending = false
currentQueue = 0
queues = [[], []]
queue = queues[currentQueue]
updateAll = ->
thisQueue = queue
currentQueue = ++currentQueue % 2
queue = queues[currentQueue]
pending = false
# set content
for item in thisQueue
data = item._impl
data.contentUpdatePending = false
if data.containsHTML
data.innerElem.innerHTML = data.text
else
data.innerElem.textContent = data.text
# measure
for item in thisQueue
data = item._impl
isAutoSize = item._autoWidth or item._autoHeight
if data.text
if isAutoSize
if data.containsHTML
updateHTMLTextSize item
else
updatePlainTextSize item
else
data.contentWidth = 0
data.contentHeight = 0
# set sizes
while item = thisQueue.pop()
data = item._impl
item.contentWidth = data.contentWidth
item.contentHeight = data.contentHeight
return
(item) ->
data = item._impl
if data.contentUpdatePending
return
queue.push item
data.contentUpdatePending = true
unless pending
setImmediate updateAll
pending = true
return
updateTextStyle = (item) ->
data = item._impl
{innerElemStyle, fontFamily} = data
{fontWeight, fontSize} = innerElemStyle
fontWeight ||= '400'
fontSize ||= '14px'
fontFamily ||= implUtils.DEFAULT_FONTS['sans-serif']
if defaultFontFamily = impl.utils.NEFT_DEFAULT_FONTS[fontFamily]
fontFamily += ", #{defaultFontFamily}"
data.font = "#{fontWeight} #{fontSize} #{fontFamily}"
return
SHEET = """
.text {
width: auto;
height: auto;
white-space: pre;
font-size: 14px;
line-height: 1;
font-family: #{implUtils.DEFAULT_FONTS['sans-serif']}, sans-serif;
margin-top: #{if impl.utils.isFirefox then 1 else 0}px;
}
.text.textVerticalCenterAlign {
top: 50%;
#{impl.utils.transformCSSProp}: translateY(-50%);
}
.text.textVerticalBottomAlign {
top: 100%;
#{impl.utils.transformCSSProp}: translateY(-100%);
}
"""
CONTAINS_HTML_RE = /<|&#/
DATA =
stylesheet: null
wrap: false
innerElem: null
innerElemStyle: null
uid: 0
contentUpdatePending: false
containsHTML: false
text: ''
font: "14px #{implUtils.DEFAULT_FONTS['sans-serif']}, sans-serif"
fontFamily: implUtils.DEFAULT_FONTS['sans-serif']
contentWidth: 0
contentHeight: 0
exports =
DATA: DATA
_createTextElement: (item) ->
data = item._impl
# innerElem
innerElem = data.innerElem = document.createElement 'span'
data.innerElemStyle = innerElem.style
# set default styles
innerElem.setAttribute 'class', 'text'
createData: impl.utils.createDataCloner 'Item', DATA
create: (data) ->
Item.create.call @, data
exports._createTextElement @
impl.utils.prependElement data.elem, data.innerElem
if impl.utils.loadingFonts['sans-serif'] > 0
impl.utils.onFontLoaded reloadFontFamily, @
setText: (val) ->
val = val.replace /<[bB][rR]\s?\/?>/g, "\n"
.text = val
.containsHTML = CONTAINS_HTML_RE.test(val)
updateContent @
return
setTextWrap: (val) ->
data =
data.wrap = val
data.innerElemStyle.whiteSpace = if val then 'pre-wrap' else 'pre'
data.innerElemStyle.width = if val then "100%" else 'auto'
return
updateTextContentSize: ->
updateContent @
return
setTextColor: (val) ->
.innerElemStyle.color = val
return
setTextLinkColor: do ->
uid = 0
(val) ->
data =
unless data.stylesheet
data.stylesheet = document.createElement 'style'
data.elem.appendChild data.stylesheet
data.innerElem.setAttribute 'id', "textLinkColor#{uid}"
data.uid = uid++
data.stylesheet.innerHTML = "#textLinkColor#{data.uid} a { color: #{val}; }"
return
setTextLineHeight: (val) ->
.innerElemStyle.lineHeight = val
updateContent @
return
setTextFontFamily: (val) ->
.fontFamily = val
if impl.utils.loadingFonts[val] > 0
impl.utils.onFontLoaded reloadFontFamily, @
if impl.utils.DEFAULT_FONTS[val]
val = "#{impl.utils.DEFAULT_FONTS[val]}, #{val}"
else
val = "'#{val}'"
.innerElemStyle.fontFamily = val
updateTextStyle @
updateContent @
return
setTextFontPixelSize: (val) ->
val = round val
.innerElemStyle.fontSize = "#{val}px"
updateTextStyle @
updateContent @
return
setTextFontWordSpacing: (val) ->
.innerElemStyle.wordSpacing = "#{val}px"
updateContent @
return
setTextFontLetterSpacing: (val) ->
.innerElemStyle.letterSpacing = "#{val}px"
updateContent @
return
setTextAlignmentHorizontal: (val) ->
.innerElemStyle.textAlign = val
return
setTextAlignmentVertical: do ->
CLASSES =
top: ''
center: 'textVerticalCenterAlign'
bottom: 'textVerticalBottomAlign'
(val) ->
.innerElem.setAttribute 'class', "text #{CLASSES[val]}"
return