function recombineTagsAndDecorations(job) {
    var sourceText = job.source;
    var extractedTags = job.extractedTags;
    var decorations = job.decorations;
    var numberLines = job.numberLines;
    var sourceNode = job.sourceNode;

    var html = [];
    // index past the last char in sourceText written to html
    var outputIdx = 0;

    var openDecoration = null;
    var currentDecoration = null;
    var tagPos = 0;  // index into extractedTags
    var decPos = 0;  // index into decorations
    var tabExpander = makeTabExpander(window['PR_TAB_WIDTH']);

    var adjacentSpaceRe = /([\r\n ]) /g;
    var startOrSpaceRe = /(^| ) /gm;
    var newlineRe = /\r\n?|\n/g;
    var trailingSpaceRe = /[ \r\n]$/;
    var lastWasSpace = true;  // the last text chunk emitted ended with a space.

    // See bug 71 and http://stackoverflow.com/questions/136443/why-doesnt-ie7-
    var isIE678 = window['_pr_isIE6']();
    var lineBreakHtml = (
        isIE678
        ? (sourceNode && sourceNode.tagName === 'PRE'
           // Use line feeds instead of <br>s so that copying and pasting works
           // on IE.
           // See Issue 104 for the derivation of this mess.
           ? (isIE678 === 6 ? '&#160;\r\n' :
              isIE678 === 7 ? '&#160;<br />\r' :
              isIE678 === 8 ? '&#160;<br />' : '&#160;\r')
           // IE collapses multiple adjacent <br>s into 1 line break.
           // Prefix every newline with '&#160;' to prevent such behavior.
           // &nbsp; is the same as &#160; but works in XML as well as HTML.
           : '&#160;<br />')
        : '<br />');

    var lineBreaker;
    if (numberLines) {
      var lineBreaks = [];
      for (var i = 0; i < 10; ++i) {
        lineBreaks[i] = lineBreakHtml + '</li><li class="L' + i + '">';
      }
      var lineNum = typeof numberLines === 'number'
          ? numberLines - 1 /* number lines are 1 indexed */ : 0;
      html.push('<ol class="linenums"><li class="L', (lineNum) % 10, '"');
      if (lineNum) {
        html.push(' value="', lineNum + 1, '"');
      }
      html.push('>');
      lineBreaker = function () {
        var lb = lineBreaks[++lineNum % 10];
        // If a decoration is open, we need to close it before closing a list-item
        // and reopen it on the other side of the list item.
        return openDecoration
            ? ('</span>' + lb + '<span class="' + openDecoration + '">') : lb;
      };
    } else {
      lineBreaker = lineBreakHtml;
    }

    // A helper function that is responsible for opening sections of decoration
    // and outputing properly escaped chunks of source
    function emitTextUpTo(sourceIdx) {
      if (sourceIdx > outputIdx) {
        if (openDecoration && openDecoration !== currentDecoration) {
          // Close the current decoration
          html.push('</span>');
          openDecoration = null;
        }
        if (!openDecoration && currentDecoration) {
          openDecoration = currentDecoration;
          html.push('<span class="', openDecoration, '">');
        }
        // This interacts badly with some wikis which introduces paragraph tags
        // into pre blocks for some strange reason.
        // It's necessary for IE though which seems to lose the preformattedness
        // of <pre> tags when their innerHTML is assigned.
        // http://stud3.tuwien.ac.at/~e0226430/innerHtmlQuirk.html
        // and it serves to undo the conversion of <br>s to newlines done in
        // chunkify.
        var htmlChunk = textToHtml(
            tabExpander(sourceText.substring(outputIdx, sourceIdx)))
            .replace(lastWasSpace
                     ? startOrSpaceRe
                     : adjacentSpaceRe, '$1&#160;');
        // Keep track of whether we need to escape space at the beginning of the
        // next chunk.
        lastWasSpace = trailingSpaceRe.test(htmlChunk);
        html.push(htmlChunk.replace(newlineRe, lineBreaker));
        outputIdx = sourceIdx;
      }
    }

    while (true) {
      // Determine if we're going to consume a tag this time around.  Otherwise
      // we consume a decoration or exit.
      var outputTag;
      if (tagPos < extractedTags.length) {
        if (decPos < decorations.length) {
          // Pick one giving preference to extractedTags since we shouldn't open
          // a new style that we're going to have to immediately close in order
          // to output a tag.
          outputTag = extractedTags[tagPos] <= decorations[decPos];
        } else {
          outputTag = true;
        }
      } else {
        outputTag = false;
      }
      // Consume either a decoration or a tag or exit.
      if (outputTag) {
        emitTextUpTo(extractedTags[tagPos]);
        if (openDecoration) {
          // Close the current decoration
          html.push('</span>');
          openDecoration = null;
        }
        html.push(extractedTags[tagPos + 1]);
        tagPos += 2;
      } else if (decPos < decorations.length) {
        emitTextUpTo(decorations[decPos]);
        currentDecoration = decorations[decPos + 1];
        decPos += 2;
      } else {
        break;
      }
    }
    emitTextUpTo(sourceText.length);
    if (openDecoration) {
      html.push('</span>');
    }
    if (numberLines) { html.push('</li></ol>'); }
    job.prettyPrintedHtml = html.join('');
  }
Breaks {@code job.source} around style boundaries in
{@code job.decorations} while re-interleaving {@code job.extractedTags},
and leaves the result in {@code job.prettyPrintedHtml}.
@param {Object} job like {
   source: {string} source as plain text,
   extractedTags: {Array.<number|string>} extractedTags chunks of raw
                  html preceded by their position in {@code job.source}
                  in order
   decorations: {Array.<number|string} an array of style classes preceded
                by the position at which they start in job.source in order
}
@private