+ computed_style: (n, prop) ->
+ if n.type is 'text'
+ n = n.parent
+ style = @iframe.contentWindow.getComputedStyle n.el, null
+ return style.getPropertyValue prop
+ # returns the new white-space value that will preserve spaces for node n
+ preserve_space: (n, ideal_target) ->
+ if n.type is 'text'
+ target = n.parent
+ else
+ target = n
+ while target isnt ideal_target and not target.el.style.whiteSpace
+ unless target?
+ console.log "bug #967123"
+ return
+ target = target.parent
+ ws = ws_props[target.el.style.whiteSpace]?.to_preserve
+ ws ?= 'pre-wrap'
+ target.el.style.whiteSpace = ws
+ @update_style_from_el target
+ return ws
+ update_style_from_el: (n) ->
+ style = n.el.getAttribute 'style'
+ if style?
+ n.attrs.style = style
+ else
+ if n.attrs.style?
+ delete n.attrs.style
+ # call this after you insert or remove inline nodes. It will:
+ # merge consecutive text nodes
+ # remove empty text nodes
+ # adjust white-space property
+ text_cleanup: (n) ->
+ if @is_display_block n
+ block = n
+ else
+ block = @find_block_parent n
+ return unless block?
+ run = @get_text_run block
+ return unless run?
+ # merge consecutive text nodes
+ if run.length > 1
+ i = 1
+ prev = run[0]
+ while i < run.length
+ n = run[i]
+ if prev.type is 'text' and n.type is 'text'
+ if prev.parent is n.parent
+ prev_i = n.parent.children.indexOf prev
+ n_i = n.parent.children.indexOf n
+ if n_i is prev_i + 1
+ prev.text = prev.text + n.text
+ prev.el.textContent = prev.text
+ @remove_node n
+ run.splice i, 1
+ continue # don't increment i or change prev
+ i += 1
+ prev = n
+ # remove empty text nodes
+ i = 0
+ while i < run.length
+ n = run[i]
+ if n.type is 'text'
+ if n.text is ''
+ @remove_node n
+ # FIXME maybe remove parents recursively if this makes them empty
+ run.splice i, 1
+ continue # don't increment i
+ i += 1
+ # note: inline tags can have white-space:pre-line/etc
+ # note: inline-blocks have their whitespace collapsed independantly of outer run
+ # note: inline-blocks are treated like non-whitespace char even if empty
+ if block.el.style.whiteSpace?
+ ws = block.el.style.whiteSpace
+ if ws_props[ws]
+ if ws_props[ws].space
+ if ws_props[ws].to_collapse is 'normal'
+ block.el.style.whiteSpace = null
+ else
+ block.el.style.whiteSpace = ws_props[ws].to_collapse
+ @update_style_from_el block
+ # note: space after <br> colapses, but not space before
+ # check for spaces that would collapse without help
+ eats_start_sp = true # if the next node starts with space it collapses (unless pre)
+ prev = null
+ for n in run
+ if n.type is 'tag'
+ if n.name is 'br'
+ eats_start_sp = true
+ else
+ eats_start_sp = false
+ else # TEXT
+ need_preserve = false
+ if n.type isnt 'text'
+ console.log "bug #232308"
+ return
+ if eats_start_sp
+ if is_space_code n.text.charCodeAt 0
+ need_preserve = true
+ unless need_preserve
+ need_preserve = multi_sp_regex.test n.text
+ if need_preserve
+ # do we have it already?
+ ws = @computed_style n, 'white-space' # FIXME implement this
+ unless ws_props[ws]?.space
+ # 2nd arg is ideal target for css rule
+ ws = @preserve_space n, block
+ eats_start_sp = false
+ else
+ if is_space_code n.text.charCodeAt(n.text.length - 1)
+ ws = @computed_style n, 'white-space' # FIXME implement this
+ if ws_props[ws]?.space
+ eats_start_sp = false
+ else
+ eats_start_sp = true
+ else
+ eats_start_sp = false
+ # check if text ends with a collapsable space
+ if run.length > 0
+ last = run[run.length - 1]
+ if last.type is 'text'
+ if eats_start_sp
+ @preserve_space last, block
+ return
+ css_clear: (n, prop) ->
+ return unless n.attrs.style?
+ return if n.attrs.style is ''
+ css_delimiter_regex = new RegExp('\s*;\s*', 'g') # FIXME make this global
+ styles = n.attrs.style.trim().split css_delimiter
+ return unless styles.length > 0
+ if styles[styles.length - 1] is ''
+ styles.pop()
+ return unless styles.length > 0
+ i = 0
+ while i < styles.length
+ if styles[i].substr(0, 12) is 'white-space:'
+ styles.splice i, 1
+ else
+ i += 1
+ return
+ # WARNING: after calling this one or more times, you MUST:
+ # if it's inline: call @text_cleanup
+ # call @changed()
+ remove_node: (n) ->
+ i = n.parent.children.indexOf n
+ if i is -1
+ throw "BUG #9187112313"
+ n.el.parentNode.removeChild n.el
+ n.parent.children.splice i, 1
+ return
+ # remove a node from the tree/dom, insert into new_parent before insert_before?end
+ # WARNING: after calling this one or more times, you MUST:
+ # if it's inline: call @text_cleanup
+ # call @changed()
+ move_node: (n, new_parent, insert_before = null) ->
+ i = n.parent.children.indexOf n
+ if i is -1
+ throw "Error: tried to remove node, but it's not in it's parents list of children"
+ return
+ if insert_before?
+ before_i = new_parent.children.indexOf insert_before
+ if i is -1
+ throw "Error: tried to move a node to be before a non-existent node"
+ insert_before = insert_before.el
+ @remove_node n
+ if insert_before?
+ new_parent.el.insertBefore n.el, insert_before
+ new_parent.children.splice before_i, 0, n
+ else
+ new_parent.el.appendChild n.el, insert_before
+ new_parent.children.push n
+ n.parent = new_parent
+ return