text: # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  # MIT License. See license.txt
  from __future__ import unicode_literals
  
  import pdfkit, os, frappe
  from frappe.utils import scrub_urls
  from frappe import _
  from bs4 import BeautifulSoup
  from PyPDF2 import PdfFileWriter, PdfFileReader
  
  def get_pdf(html, options=None, output = None):
  	html = scrub_urls(html)
  	html, options = prepare_options(html, options)
  	fname = os.path.join("/tmp", "frappe-pdf-{0}.pdf".format(frappe.generate_hash()))
  
  	try:
  		pdfkit.from_string(html, fname, options=options or {})
  		if output:
  			append_pdf(PdfFileReader(file(fname,"rb")),output)
  		else:
  			with open(fname, "rb") as fileobj:
  				filedata = fileobj.read()
  
  	except IOError as e:
  		if ("ContentNotFoundError" in e.message
  			or "ContentOperationNotPermittedError" in e.message
  			or "UnknownContentError" in e.message
  			or "RemoteHostClosedError" in e.message):
  
  			# allow pdfs with missing images if file got created
  			if os.path.exists(fname):
  				with open(fname, "rb") as fileobj:
  					filedata = fileobj.read()
  
  			else:
  				frappe.throw(_("PDF generation failed because of broken image links"))
  		else:
  			raise
  
  	finally:
  		cleanup(fname, options)
  
  	if output:
  		return output
  
  	return filedata
  
  def append_pdf(input,output):
  	# Merging multiple pdf files
      [output.addPage(input.getPage(page_num)) for page_num in range(input.numPages)]
  
  def prepare_options(html, options):
  	if not options:
  		options = {}
  
  	options.update({
  		'print-media-type': None,
  		'background': None,
  		'images': None,
  		'quiet': None,
  		# 'no-outline': None,
  		'encoding': "UTF-8",
  		#'load-error-handling': 'ignore',
  
  		# defaults
  		'margin-right': '15mm',
  		'margin-left': '15mm'
  	})
  
  	html, html_options = read_options_from_html(html)
  	options.update(html_options or {})
  
  	# cookies
  	if frappe.session and frappe.session.sid:
  		options['cookie'] = [('sid', '{0}'.format(frappe.session.sid))]
  
  	# page size
  	if not options.get("page-size"):
  		options['page-size'] = frappe.db.get_single_value("Print Settings", "pdf_page_size") or "A4"
  
  	return html, options
  
  def read_options_from_html(html):
  	options = {}
  	soup = BeautifulSoup(html, "html5lib")
  
  	options.update(prepare_header_footer(soup))
  
  	toggle_visible_pdf(soup)
  
  	# extract pdfkit options from html
  	for html_id in ("margin-top", "margin-bottom", "margin-left", "margin-right", "page-size"):
  		try:
  			tag = soup.find(id=html_id)
  			if tag and tag.contents:
  				options[html_id] = tag.contents
  		except:
  			pass
  
  	return soup.prettify(), options
  
  def prepare_header_footer(soup):
  	options = {}
  
  	head = soup.find("head").contents
  	styles = soup.find_all("style")
  
  	bootstrap = frappe.read_file(os.path.join(frappe.local.sites_path, "assets/frappe/css/bootstrap.css"))
  	fontawesome = frappe.read_file(os.path.join(frappe.local.sites_path, "assets/frappe/css/font-awesome.css"))
  
  	# extract header and footer
  	for html_id in ("header-html", "footer-html"):
  		content = soup.find(id=html_id)
  		if content:
  			# there could be multiple instances of header-html/footer-html
  			for tag in soup.find_all(id=html_id):
  				tag.extract()
  
  			toggle_visible_pdf(content)
  			html = frappe.render_template("templates/print_formats/pdf_header_footer.html", {
  				"head": head,
  				"styles": styles,
  				"content": content,
  				"html_id": html_id,
  				"bootstrap": bootstrap,
  				"fontawesome": fontawesome
  			})
  
  			# create temp file
  			fname = os.path.join("/tmp", "frappe-pdf-{0}.html".format(frappe.generate_hash()))
  			with open(fname, "w") as f:
  				f.write(html.encode("utf-8"))
  
  			# {"header-html": "/tmp/frappe-pdf-random.html"}
  			options[html_id] = fname
  		else:
  			if html_id == "header-html":
  				options["margin-top"] = "15mm"
  			elif html_id == "footer-html":
  				options["margin-bottom"] = "15mm"
  
  	return options
  
  def cleanup(fname, options):
  	if os.path.exists(fname):
  		os.remove(fname)
  
  	for key in ("header-html", "footer-html"):
  		if options.get(key) and os.path.exists(options[key]):
  			os.remove(options[key])
  
  def toggle_visible_pdf(soup):
  	for tag in soup.find_all(attrs={"class": "visible-pdf"}):
  		# remove visible-pdf class to unhide
  		tag.attrs['class'].remove('visible-pdf')
  
  	for tag in soup.find_all(attrs={"class": "hidden-pdf"}):
  		# remove tag from html
  		tag.extract()
  