text: // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  // MIT License. See license.txt
  
  // default print style
  _p.def_print_style_body = "html, body, div, span, td, p { \
  		font-family: inherit; \
  		font-size: inherit; \
  	}\
  	.page-settings {\
  		font-family: Helvetica, 'Open Sans', sans-serif;\
  		font-size: 9pt;\
  	}\
  	pre { margin:0; padding:0;}";
  
  _p.def_print_style_other = "\n.simpletable, .noborder { \
  		border-collapse: collapse;\
  		margin-bottom: 10px;\
  	}\
  	.simpletable td {\
  		border: 1pt solid #777;\
  		vertical-align: top;\
  		padding: 4px;\
  	}\
  	.noborder td {\
  		vertical-align: top;\
  	}";
  
  _p.go = function(html) {
  	var w = _p.preview(html);
  	w.print();
  	w.close();
  }
  
  _p.preview = function(html) {
  	var w = window.open();
  	if(!w) {
  		msgprint(__("Please enable pop-ups"));
  		return;
  	}
  	w.document.write(html);
  	w.document.close();
  	return w
  }
  
  _f.get_value = function(dt, dn, fn) {
  	if(locals[dt] && locals[dt][dn])
  		return locals[dt][dn][fn];
  }
  
  // _p can be referenced as this inside $.extend
  $.extend(_p, {
  	show_dialog: function() {
  		if(!_p.dialog) {
  			_p.make_dialog();
  		}
  		_p.dialog.show();
  	},
  
  	make_dialog: function() {
  		// Prepare Dialog Box Layout
  		var dialog = new frappe.ui.Dialog({
  			title: "Print Formats",
  			fields: [
  				{fieldtype:"Select", label:"Print Format", fieldname:"print_format", reqd:1},
  				{fieldtype:"Check", label:"No Letter Head", fieldname:"no_letterhead"},
  				{fieldtype:"HTML", options: '<p style="text-align: right;">\
  					<button class="btn btn-primary btn-print">Print</button>\
  					<button class="btn btn-default btn-preview">Preview</button>\
  				</p>'},
  			]
  		});
  
  		dialog.$wrapper.find(".btn-print").click(function() {
  			var args = dialog.get_values();
  			_p.build(
  				args.print_format, // fmtname
  				_p.go, // onload
  				args.no_letterhead // no_letterhead
  			);
  		});
  
  		dialog.$wrapper.find(".btn-preview").click(function() {
  			var args = dialog.get_values();
  			_p.build(
  				args.print_format, // fmtname
  				_p.preview, // onload
  				args.no_letterhead // no_letterhead
  			);
  		});
  
  		dialog.on_page_show = function() {
  			var $print = dialog.fields_dict.print_format.$input;
  			$print.empty().add_options(cur_frm.print_preview.print_formats);
  
  			if(cur_frm.$print_view_select && cur_frm.$print_view_select.val())
  				$print.val(cur_frm.$print_view_select.val());
  		}
  
  		_p.dialog = dialog;
  	},
  
  	// Define formats dict
  	formats: {},
  
  	/* args dict can contain:
  			+ fmtname --> print format name
  			+ onload
  			+ no_letterhead
  			+ only_body
  	*/
  	build: function(fmtname, onload, no_letterhead, only_body, no_heading) {
  		if(!fmtname) {
  			fmtname= "Standard";
  		}
  
  		args = {
  			fmtname: fmtname,
  			onload: onload,
  			no_letterhead: no_letterhead,
  			only_body: only_body
  		};
  
  		if(!cur_frm) {
  			msgprint(__("No document selected"));
  			return;
  		}
  
  		if(!frappe.model.can_print(cur_frm.doctype, cur_frm)) {
  			msgprint(__("You are not allowed to print this document"));
  			return;
  		}
  
  		// Get current doc (record)
  		var doc = locals[cur_frm.doctype][cur_frm.docname];
  		if(args.fmtname == 'Standard') {
  			args.onload(_p.render({
  				body: _p.print_std(args.no_letterhead, no_heading),
  				style: _p.print_style,
  				doc: doc,
  				title: doc.name,
  				no_letterhead: args.no_letterhead,
  				no_heading: no_heading,
  				only_body: args.only_body
  			}));
  		} else {
  			var print_format_doc = locals["Print Format"][args.fmtname];
  			if(!print_format_doc) {
  				msgprint(__("Unknown Print Format: {0}", [args.fmtname]));
  				return;
  			}
  			args.onload(_p.render({
  				body: print_format_doc.html,
  				style: '',
  				doc: doc,
  				title: doc.name,
  				no_letterhead: args.no_letterhead,
  				no_heading: no_heading,
  				only_body: args.only_body
  			}));
  		}
  	},
  
  	render: function(args) {
  		var container = document.createElement('div');
  		var stat = '';
  
  		if(!args.no_heading) {
  			// if draft/archived, show draft/archived banner
  			stat += _p.show_draft(args);
  			stat += _p.show_archived(args);
  			stat += _p.show_cancelled(args);
  		}
  
  		// Append args.body's content as a child of container
  		container.innerHTML = args.body;
  
  		// Show letterhead?
  		_p.show_letterhead(container, args);
  
  		_p.run_embedded_js(container, args.doc);
  
  		var style = _p.consolidate_css(container, args);
  
  		_p.render_header_on_break(container, args);
  
  		return _p.render_final(style, stat, container, args);
  	},
  
  
  	head_banner_format: function() {
  		return "\
  			<div style = '\
  					text-align: center; \
  					padding: 8px; \
  					background-color: #CCC;'> \
  				<div style = '\
  						font-size: 20px; \
  						font-weight: bold;'>\
  					{{HEAD}}\
  				</div>\
  				{{DESCRIPTION}}\
  			</div>"
  	},
  
  	/*
  		Check if doc's status is not submitted (docstatus == 0)
  		and submission is pending
  
  		Display draft in header if true
  	*/
  	show_draft: function(args) {
  		var is_doctype_submittable = 0;
  		var plist = locals['DocPerm'];
  		for(var perm in plist) {
  			var p = plist[perm];
  			if((p.parent==args.doc.doctype) && (p.submit==1)){
  				is_doctype_submittable = 1;
  				break;
  			}
  		}
  
  		if(args.doc && cint(args.doc.docstatus)==0 && is_doctype_submittable) {
  			draft = _p.head_banner_format();
  			draft = draft.replace("{{HEAD}}", "DRAFT");
  			draft = draft.replace("{{DESCRIPTION}}", "This box will go away after the document is submitted.");
  			return draft;
  		} else {
  			return "";
  		}
  	},
  
  
  	/*
  		Check if doc is archived
  		Display archived in header if true
  	*/
  	show_archived: function(args) {
  		if(args.doc && args.doc.__archived) {
  			archived = _p.head_banner_format();
  			archived = archived.replace("{{HEAD}}", "ARCHIVED");
  			archived = archived.replace("{{DESCRIPTION}}", "You must restore this document to make it editable.");
  			return archived;
  		} else {
  			return "";
  		}
  	},
  
  
  	/*
  		Check if doc is cancelled
  		Display cancelled in header if true
  	*/
  	show_cancelled: function(args) {
  		if(args.doc && args.doc.docstatus==2) {
  			cancelled = _p.head_banner_format();
  			cancelled = cancelled.replace("{{HEAD}}", "CANCELLED");
  			cancelled = cancelled.replace("{{DESCRIPTION}}", "You must amend this document to make it editable.");
  			return cancelled;
  		} else {
  			return "";
  		}
  	},
  
  
  	consolidate_css: function(container, args) {
  		// Extract <style> content from container
  		var body_style = '';
  		var style_list = container.getElementsByTagName('style');
  		while(style_list && style_list.length>0) {
  			for(i in style_list) {
  				if(style_list[i] && style_list[i].innerHTML) {
  					body_style += style_list[i].innerHTML;
  					var parent = style_list[i].parentNode;
  					if(parent) {
  						parent.removeChild(style_list[i]);
  					} else {
  						container.removeChild(style_list[i]);
  					}
  				}
  			}
  			style_list = container.getElementsByTagName('style');
  		}
  
  		// Concatenate all styles
  		style_concat =  (args.only_body ? '' : _p.def_print_style_body)
  				+ _p.def_print_style_other + args.style + body_style;
  
  		return style_concat;
  	},
  
  
  	// This is used to calculate and substitude values in the HTML
  	run_embedded_js: function(container, doc) {
  		var script_list = $(container).find("script");
  
  		for(var i=0, j=script_list.length; i<j; i++) {
  			var element = script_list[i];
  			var code = element.innerHTML;
  			try {
  				var new_html = code ? (eval(code) || "") : "";
  			} catch(e) {
  				console.log("Error in Custom Script:" + e + "\n" + code);
  				console.trace(e);
  				throw e;
  			}
  			if(in_list(["string", "number"], typeof new_html)) {
  				$(element).replaceWith(this.add_span(new_html + ""));
  			}
  		}
  
  		// remove scripts once executed
  		$(container).find("script").remove();
  	},
  
  	add_span: function(html) {
  		var tags = ["<span", "<p", "<div", "<br", "<table"];
  		var match = false;
  		for(var i=0; i<tags.length; i++) {
  			if(html.match(tags[i])) {
  				match = true;
  				break;
  			}
  		}
  
  		if(!match) {
  			html = "<span>" + html + "</span>";
  		}
  
  		return html;
  	},
  
  
  	// Attach letterhead at top of container
  	show_letterhead: function(container, args) {
  		if(!args.no_letterhead) {
  			container.innerHTML = '<div style="max-width: 100%">'
  				+ _p.get_letter_head() + '</div>'
  				+ container.innerHTML;
  		}
  	},
  
  
  	render_header_on_break: function(container, args) {
  		var page_set = container.getElementsByClassName('page-settings');
  		if(page_set.length) {
  			for(var i = 0; i < page_set.length; i++) {
  				var tmp = '';
  				// if draft/archived, show draft/archived banner
  				tmp += _p.show_draft(args);
  				tmp += _p.show_archived(args);
  				_p.show_letterhead(page_set[i], args);
  				page_set[i].innerHTML = tmp + page_set[i].innerHTML;
  			}
  		}
  	},
  
  
  	// called by _p.render for final render of print
  	render_final: function(style, stat, container, args) {
  		if(!args.only_body) {
  			var header = '<!DOCTYPE html>\
  <html>\
  	<head>\
  		<meta charset="utf-8" />\
  		<title>' + args.title + '</title>\
  		<style>' + style + '</style>\
  	</head>\
  	<body>';
  			var footer = '\
  	</body>\
  </html>';
  		} else {
  			var header = '';
  			var footer = '';
  		}
  		var finished =  header
  			+ '<div class="page-settings">'
  			+ stat
  			+ container.innerHTML
  			+ '</div>'
  			+ footer;
  
  
  		// replace relative links by absolute links
  		var prefix = window.location.href.split("desk")[0]
  		// find unique matches
  		var matches = $.unique(finished.match(/src=['"]([^'"]*)['"]/g) || []);
  
  		$.each(matches, function(i, v) {
  			if(v.substr(0,4)=="src=") {
  				var v = v.substr(5, v.length-6);
  				if(v.substr(0,4)!="http") {
  					finished = finished.split(v).join(prefix + lstrip(v, "/"));
  				}
  
  			}
  		});
  
  		return finished;
  	},
  
  
  	// fetches letter head from current doc or control panel
  	get_letter_head: function() {
  		var lh = '';
  		if(cur_frm.doc.letter_head) {
  			lh = cstr(frappe.boot.letter_heads[cur_frm.doc.letter_head].header);
  		} else if (frappe.boot.sysdefaults.default_letter_head_content) {
  			lh = frappe.boot.sysdefaults.default_letter_head_content;
  		}
  		return lh;
  	},
  
  	// common print style setting
  	print_style: "\
  		.datalabelcell { \
  			padding: 2px 0px; \
  			width: 38%; \
  			vertical-align: top; \
  			} \
  		.datainputcell { \
  			padding: 2px 0px; \
  			width: 62%; \
  			text-align: left; \
  			}\
  		.sectionHeading { \
  			font-size: 16px; \
  			font-weight: bold; \
  			margin: 8px 0px; \
  			} \
  		.columnHeading { \
  			font-size: 14px; \
  			font-weight: bold; \
  			margin: 8px 0px; \
  			}",
  
  	print_std: function(no_letterhead, no_heading) {
  		// Get doctype, docname, layout for a doctype
  		var docname = cur_frm.docname;
  		var doctype = cur_frm.doctype;
  		var data = frappe.get_children("DocType", doctype, "fields");
  		var layout = _p.add_layout(doctype);
  		this.pf_list = [layout];
  		var me = this;
  		me.layout = layout;
  
  		$.extend(this, {
  			build_head: function(data, doctype, docname) {
  				// Heading
  				var h1_style = {
  					fontSize: '22px',
  					marginBottom: '8px'
  				}
  				var h1 = $a(me.layout.cur_row.header, 'h1', '', h1_style);
  
  				// Get print heading
  				if (cur_frm.pformat[docname]) {
  					// first check in cur_frm.pformat
  					h1.innerHTML = cur_frm.pformat[docname];
  				} else {
  					// then check if select print heading exists and has a value
  					var val = null;
  					for (var i = 0; i < data.length; i++) {
  						if (data[i].fieldname === 'select_print_heading') {
  							val = _f.get_value(doctype, docname, data[i].fieldname);
  							break;
  						}
  					}
  					// if not, just have doctype has heading
  					h1.innerHTML = val ? val : __(doctype);
  				}
  
  				var h2_style = {
  					fontSize: '16px',
  					color: '#888',
  					marginBottom: '8px',
  					paddingBottom: '8px',
  					borderBottom: (me.layout.with_border ? '0px' :
  						'1px solid #000')
  				}
  				var h2 = $a(me.layout.cur_row.header, 'div', '', h2_style);
  				h2.innerHTML = docname;
  
  				if(cur_frm.state_fieldname && !cur_frm.fields_dict[cur_frm.state_fieldname].df.print_hide) {
  					$a(h2, 'br');
  					var span = $a(h2, 'span', '',
  						{padding: "3px", color: "#fff", backgroundColor: "#777",
  							display:"inline-block"});
  					span.innerHTML = cur_frm.doc[cur_frm.state_fieldname];
  				}
  			},
  
  			build_data: function(data, doctype, docname) {
  				// Start with a row and a cell in that row
  				if(data[0] && data[0].fieldtype != "Section Break") {
  					me.layout.addrow();
  					if(data[0].fieldtype != "Column Break") {
  						me.layout.addcell();
  					}
  				}
  
  				$.extend(this, {
  					generate_custom_html: function(field, doctype, docname) {
  						var container = $a(me.layout.cur_cell, 'div');
  						container.innerHTML = cur_frm.pformat[field.fieldname](locals[doctype][docname]);
  					},
  
  					render_normal: function(field, data, i) {
  						switch(field.fieldtype) {
  							case 'Fold': break;
  							case 'Section Break':
  								me.layout.addrow();
  
  								// Add column if no column break after this field
  								if(data[i+1] && data[i+1].fieldtype !=
  										'Column Break') {
  									me.layout.addcell();
  								}
  								break;
  
  							case 'Column Break':
  								me.layout.addcell(field.width, field.label);
  								break;
  
  							case 'Table':
  								var table = print_table(
  										doctype, // dt
  										docname, // dn
  										field.fieldname,
  										field.options, // tabletype
  										null, // cols
  										null, // head_labels
  										null, // widths
  										null); // condition
  								me.layout = _p.print_std_add_table(table, me.layout, me.pf_list, doctype, no_letterhead);
  								break;
  
  							case 'HTML':
  								var div = $a(me.layout.cur_cell, 'div');
  								div.innerHTML = cstr(field.options);
  								break;
  
  							case 'Code':
  								var div = $a(me.layout.cur_cell, 'div');
  								var val = _f.get_value(doctype, docname,
  									field.fieldname);
  								div.innerHTML = '<div>' + __(field.label) +
  									': </div><pre style="font-family: Courier, Fixed;">' + (val ? val : '') +
  									'</pre>';
  								break;
  
  							case 'Text Editor':
  								var div = $a(me.layout.cur_cell, 'div');
  								var val = _f.get_value(doctype, docname,
  									field.fieldname);
  								div.innerHTML = val ? val : '';
  								break;
  
  							default:
  								// Add Cell Data
  								_p.print_std_add_field(doctype, docname, field, me.layout);
  								break;
  						}
  					}
  				});
  
  				// Then build each field
  				for(var i = 0; i < data.length; i++) {
  					var fieldname = data[i].fieldname ? data[i].fieldname :
  						data[i].label;
  					var field = fieldname ?
  						frappe.meta.get_docfield(doctype, fieldname, docname) : data[i];
  					if(!field.print_hide) {
  						if(cur_frm.pformat[field.fieldname]) {
  							// If there is a custom method to generate the HTML, then use it
  							this.generate_custom_html(field, doctype, docname);
  						} else {
  							// Do the normal rendering
  							this.render_normal(field, data, i);
  						}
  					}
  				}
  				me.layout.close_borders();
  			},
  
  			build_html: function() {
  				var html = '';
  				for(var i = 0; i < me.pf_list.length; i++) {
  					if(me.pf_list[i].wrapper) {
  						html += me.pf_list[i].wrapper.innerHTML;
  					} else if(me.pf_list[i].innerHTML) {
  						html += me.pf_list[i].innerHTML;
  					} else {
  						html += me.pf_list[i];
  					}
  				}
  				this.pf_list = [];
  				return html;
  			}
  		});
  
  		if(!no_heading) {
  			this.build_head(data, doctype, docname);
  		}
  
  		this.build_data(data, doctype, docname);
  
  		var html = this.build_html();
  		return html;
  	},
  
  	add_layout: function(doctype) {
  		var layout = new Layout();
  		layout.addrow();
  
  		if(locals['DocType'][doctype].print_outline == 'Yes') {
  			layout.with_border = 1
  		}
  
  		return layout;
  	},
  
  	print_std_add_table: function(t, layout, pf_list, dt, no_letterhead) {
  		if(t.appendChild) {
  			// If only one table is passed
  			layout.cur_cell.appendChild(t);
  		} else {
  			page_break = '\n\
  				<div style = "page-break-after: always;" \
  				class = "page_break"></div><div class="page-settings"></div>';
  
  			// If a list of tables is passed
  			for(var i = 0; i < t.length-1; i++) {
  				// add to current page
  				layout.cur_cell.appendChild(t[i]);
  				layout.close_borders();
  
  				pf_list.push(page_break);
  
  				// Create new page
  				layout = _p.add_layout(dt, no_letterhead);
  				pf_list.push(layout);
  
  				layout.addrow();
  				layout.addcell();
  
  				var div = $a(layout.cur_cell, 'div');
  				div.innerHTML = 'Continued from previous page...';
  				div.style.padding = '4px';
  			}
  
  			// Append last table
  			layout.cur_cell.appendChild(t[t.length-1]);
  		}
  		return layout;
  	},
  
  	print_std_add_field: function(dt, dn, f, layout) {
  		var val = _f.get_value(dt, dn, f.fieldname);
  		if(f.fieldtype!='Button') {
  			if(val || in_list(['Float', 'Int', 'Currency'], f.fieldtype)) {
  				// If value or a numeric type then proceed
  
  				// Add field table
  				row = _p.field_tab(layout.cur_cell);
  
  				// Add label
  				row.cells[0].innerHTML = __(f.label ? f.label : f.fieldname);
  				row.cells[1].innerHTML = frappe.format(val, f, {for_print: true});
  
  				// left align currency in normal display
  				if(f.fieldtype == 'Currency') {
  					$y(row.cells[1], { textAlign: 'left' });
  				}
  
  			}
  		}
  	},
  
  	field_tab: function(layout_cell) {
  		var tab = $a(layout_cell, 'table', '', {width:'100%'});
  		var row = tab.insertRow(0);
  		_p.row = row; // Don't know this line's purpose
  		row.insertCell(0);
  		row.insertCell(1);
  		row.cells[0].className = 'datalabelcell';
  		row.cells[0].style.width = "38%";
  		row.cells[1].className = 'datainputcell';
  		return row;
  	}
  });
  