File:  [LON-CAPA] / modules / damieng / wkhtmltopdf_test / print_column_layout.js
Revision 1.1: download - view: text, annotated - select for diffs
Thu Jan 26 17:46:45 2017 UTC (7 years, 2 months ago) by damieng
Branches: MAIN
CVS tags: HEAD
added wkhtmltopdf_test to print to PDF from HTML documents

var LCPRINT = function () {
  
  var paper_width = 11; // in inches
  var paper_height = 8.5;
  
  /**
   * Sets paper width and height, in inches.
   */
  function set_paper_size(width, height) {
    paper_width = width;
    paper_height = height;
  }
  
  /**
   * Returns a new table element with one row and the given number of columns, using given style for td.
   */
  function new_table(columns, cell_style) {
    var table, tr, td, i;
    table = document.createElement('table');
    table.setAttribute('class', 'page-table');
    document.body.appendChild(table);
    tr = document.createElement('tr');
    table.appendChild(tr);
    for (i=0; i<columns; i++) {
      td = document.createElement('td');
      td.setAttribute('style', cell_style);
      tr.appendChild(td);
    }
    return(table);
  }
  
  /**
   * Webkit-only solution to get the CSS rules applied to a node (not the computed ones)
   */
  function getAppliedCSS(node) {
    var css = ''
    var style_attribute = node.getAttribute('style');
    if (style_attribute != null)
      css += style_attribute;
    var rules = node.ownerDocument.defaultView.getMatchedCSSRules(node, '');
    if (rules != null) {
      var i = rules.length;
      while (i--) {
        css += rules[i].style.cssText + ';';
      }
    }
    if (css == '')
      return null;
    return css;
  }
  
  /**
   * Returns the value of a CSS attribute from the node style attribute, or null if not defined.
   */
  function style_value(node, name) {
    var style_attribute = node.getAttribute('style');
    if (style_attribute == null)
      return(null);
    style_attribute = style_attribute.replace(/\s/, '');
    var css_pairs = style_attribute.split(';');
    for (var i=0; i<css_pairs.length; i++) {
      var name_value = css_pairs[i].split(':');
      if (name_value.length == 2 && name_value[0] == name)
        return(name_value[1]);
    }
  }
  
  /**
   * Split the node if it can be, and return true if it was split.
   */
  function split_node(node, nodes, i, appliedCSS) {
    var i,j;
    var child, little_divs, little_div, next_child, future_divs;
    // check if there is something to split
    future_divs = 0;
    for (child=node.firstChild; child != null; child=child.nextSibling) {
      if (child.nodeType == 3 && child.nodeValue.trim() == '') {
        continue;
      }
      if (future_divs == 0 || child.offsetHeight > 0) {
        future_divs++;
      }
    }
    if (future_divs > 1) {
      
      // split the div into little divs with the same style
      little_divs = [];
      little_div = null;
      for (child=node.firstChild; child != null; child=next_child) {
        //console.log(child);
        next_child = child.nextSibling;
        if (child.nodeType == 3 && child.nodeValue.trim() == '') {
          continue;
        }
        if (little_div == null || child.offsetHeight > 0) {
          // only create another div if the child has a height, otherwise keep it with previous div
          little_div = document.createElement('div');
          if (appliedCSS != null)
            little_div.style.cssText = appliedCSS;
          little_divs.push(little_div);
        }
        node.removeChild(child);
        little_div.appendChild(child);
      }
      nodes.splice(i, 1);
      for (j=little_divs.length-1; j >= 0; j--) {
        nodes.splice(i, 0, little_divs[j]);
      }
      return true;
    }
    return false;
  }
  
  /**
   * Changes layout to given number of columns, and changes window status to "done".
   * Should only be called once, after page loading.
   */
  function layout(columns) {
    console.log('column layout...');
    // This function puts all the body children in tables, one table for each page.
    // It inserts each node in the current cell, and checks if the table extends beyond the page height.
    // If it does, it switches to the next cell in the table or the next table and moves the node there.
    // It works with CSS "page-break-before: always" and "page-break-after: always", but only at the top level.
    // For column break, use "break-before: column" or "break-after: column" on a top level element style attribute.
    // top-level div elements without "page-break-inside: avoid" can now be split.
    var i;
    var nodes = [];
    var node;
    for (node=document.body.firstChild; node != null; node=node.nextSibling)
      nodes.push(node);
    var page_height = paper_width * 96; // 96 dpi
    var table_height;
    var body_width = paper_height * 96;
    var cell_width = body_width/columns;
    var cell_style = 'min-width:'+(cell_width-1)+'px; max-width:'+cell_width+'px';
    var table = new_table(columns, cell_style);
    var td = table.firstChild.firstChild;
    var computed_style, appliedCSS, child_computed_style;
    var page_break_before = false;
    var column_break_before = false;
    var avoid_page_break_inside = false;
    for (i=0; i<nodes.length; i++) {
      node = nodes[i];
      if (node.parentNode != null)
        node.parentNode.removeChild(node);
      td.appendChild(node);
      if (node.nodeType == 1) {
        computed_style = window.getComputedStyle(node);
        appliedCSS = getAppliedCSS(node);
      } else {
        computed_style = null;
        appliedCSS = null;
      }
      page_break_before = page_break_before || (computed_style != null &&
        (computed_style.getPropertyValue('page-break-before') == 'always' ||
        computed_style.getPropertyValue('break-before') == 'page' ||
        style_value(node, 'break-before') == 'page'));
      if (computed_style != null && page_break_before)
        node.style.pageBreakBefore = 'auto';
      if (!page_break_before && node.nodeName == 'DIV' && node.firstChild != null) {
        // look for a page break before in the first child of a div
        child_computed_style = window.getComputedStyle(node.firstChild);
        if (child_computed_style != null && (
            child_computed_style.getPropertyValue('page-break-before') == 'always' ||
            child_computed_style.getPropertyValue('break-before') == 'page' ||
            style_value(node.firstChild, 'break-before') == 'page')) {
          page_break_before = true;
          node.firstChild.style.pageBreakBefore = 'auto';
        }
      }
      column_break_before = column_break_before || (computed_style != null &&
        computed_style.getPropertyValue('break-before') == 'column');
      // unfortunately, browsers often do not support break-before, so we need to look at the style attribute directly
      // (this means CSS in .css files will be ignored for break-before)
      if (!column_break_before && node.nodeType == 1) {
        if (style_value(node, 'break-before') == 'column')
          column_break_before = true;
      }
      if (!column_break_before && node.nodeName == 'DIV' && node.firstChild != null && node.firstChild.nodeType == 1) {
        // look for a column break before in the first child of a div
        child_computed_style = window.getComputedStyle(node.firstChild);
        if ((child_computed_style != null && child_computed_style.getPropertyValue('break-before') == 'column') ||
            style_value(node.firstChild, 'break-before') == 'column') {
          column_break_before = true;
        }
      }
      avoid_page_break_inside = (computed_style != null &&
        computed_style.getPropertyValue('page-break-inside') == 'avoid');
      table_height = table.offsetHeight;
      if (table_height > page_height || page_break_before || column_break_before) {
        if (!page_break_before && !column_break_before && !avoid_page_break_inside &&
            node.nodeName == 'DIV' && node.childNodes.length > 1) {
          if (split_node(node, nodes, i, appliedCSS)) {
            td.removeChild(node);
            i--;
            page_break_before = false;
            column_break_before = false;
            continue;
          }
        }
        td.removeChild(node);
        if (td.nextSibling == null || page_break_before) {
          table = new_table(columns, cell_style);
          td = table.firstChild.firstChild;
        } else {
          td = td.nextSibling;
        }
        td.appendChild(node);
      }
      page_break_before = (computed_style != null && (computed_style.getPropertyValue('page-break-after') == 'always' ||
        computed_style.getPropertyValue('break-after') == 'page' ||
        style_value(node, 'break-after') == 'page'));
      if (page_break_before)
        node.style.pageBreakAfter = 'auto';
      if (!page_break_before && node.nodeName == 'DIV' && node.lastChild != null) {
        // look for a page break after in the last child of a div
        child_computed_style = window.getComputedStyle(node.lastChild);
        if (child_computed_style != null && (
            child_computed_style.getPropertyValue('page-break-after') == 'always' ||
            child_computed_style.getPropertyValue('break-after') == 'page' ||
            style_value(node.lastChild, 'break-after') == 'page')) {
          page_break_before = true;
          node.lastChild.style.pageBreakBefore = 'auto';
        }
      }
      column_break_before = (computed_style != null && computed_style.getPropertyValue('break-after') == 'column');
      if (!column_break_before && node.nodeType == 1) {
        if (style_value(node, 'break-after') == 'column')
          column_break_before = true;
      }
      if (!column_break_before && node.nodeName == 'DIV' && node.lastChild != null) {
        // look for a column break after in the last child of a div
        child_computed_style = window.getComputedStyle(node.lastChild);
        if (child_computed_style != null && (
            child_computed_style.getPropertyValue('break-after') == 'column' ||
            style_value(node.lastChild, 'break-after') == 'column')) {
          column_break_before = true;
        }
      }
    }
    console.log('layout done');
    window.status = "done";
  }

  return({
    "set_paper_size": set_paper_size,
    "layout": layout
  });
}();

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>