Source: custom-plugins/plugin-custom-create-pdf.js

/** @module */

import jsPDF from 'jspdf'
import { getParam } from '../modules/url-state.js'

const dimensions = {
  a0: [1189, 841],
  a1: [841, 594],
  a2: [594, 420],
  a3: [420, 297],
  a4: [297, 210],
  a5: [210, 148]
}
const mmPerInch = 25.4
const margin = 64
const font_size = 32
const spacing = font_size * 1 // spacing between text
// the footer height is two lines of text, spacing bewtween them and an extra space at the bottom
const footer_height = font_size * 2 + spacing * 2
const logo_width = 300
const logo_height_fallback = 74 // firefox can't get the natural height of the image

// Initialize logo image so it's ready when needed
const logo_image = new Image()
logo_image.src = 'img/logos/vurdst-logo.svg'

/**
 * Formats a date to match the required specifications of dd.mm.yy hh:MM
 * @param {Date} date The Date object to format
 * @returns The formated date
 */
function formatDate(date) {
  let formated_date = ''
  const day = date.getDate() + ''
  formated_date += (day.length < 2 ? '0' : '') + day + '.'
  const month = date.getMonth() + 1 + ''
  formated_date += (month.length < 2 ? '0' : '') + month + '.'
  formated_date += (date.getFullYear() + ' ').substring(2)
  const hours = date.getHours() + ''
  formated_date += (hours.length < 2 ? '0' : '') + hours + ':'
  const minutes = date.getMinutes() + ''
  formated_date += (minutes.length < 2 ? '0' : '') + minutes
  return formated_date
}

/**
 * Gets the address as an object, split into road_name, house_number, post_number, and post_name.
 * @returns {Object}
 */
function getAddressObject() {
  const address = getParam('address') || ''
  const comma_split = address.split(',')
  if (!comma_split[1]) {
    return {
      road_name: '',
      house_number: '',
      post_number: '',
      post_name: ''
    }
  }
  const road = comma_split[0]
  const last_index = road.lastIndexOf(' ')
  const road_name = road.slice(0, last_index)
  const house_number = road.slice(last_index + 1)

  const post = comma_split[1].slice(1)
  const first_index = post.indexOf(' ')
  const post_number = post.slice(0, first_index)
  const post_name = post.slice(first_index + 1)

  return {
    road_name,
    house_number,
    post_number,
    post_name
  }
}

/**
 * Generates a file name with the following syntax: 'Skraafoto_{vejnavn}_{husnr}_{postnr}_{postnavn}_{date}
 * Where the date is the date when the photo was taken in the format DDMMYY
 * @param {Object} item The item to generate a file name for.
 * @returns {String} The generated file name.
 */
function generateFileName(item) {
  const address = getAddressObject()
  const date = new Date(item.properties.datetime)
  const month = date.getMonth() + 1 + ''
  const day = date.getDate() + ''
  const formated_date = (day.length < 2 ? '0' : '') + day + (month.length < 2 ? '0' : '') + month + (date.getFullYear() + '').slice(2)
  return `Skraafoto-${ address.road_name }_${ address.house_number }_${ address.post_number }_${ address.post_name }_${ formated_date }.pdf`
}

/** Draws a custom image footer */
function drawFooterContent(height, width, item) {

  const canvas = document.createElement('canvas')
  canvas.height = height
  canvas.width = width

  const today = formatDate(new Date())
  const ctx = canvas.getContext('2d')

  const capture_date = formatDate(new Date(item.properties.datetime)) //.slice(0, -6)
  const address = getParam('address') || ''
  const vurd_id = getParam('ejendomsid') || ''
  
  // Write some information onto the canvas
  ctx.fillStyle = '#000'
  ctx.font = font_size + 'px sans-serif'
  ctx.textBaseline = 'top'

  ctx.textAlign = 'left'
  ctx.fillText(`${ address }`, 0, 0)
  ctx.fillText(`VurderingsejendomsID: ${ vurd_id }`, 0, font_size + spacing)

  ctx.textAlign = 'right'
  ctx.fillText(`Dokument dannet: ${ today }`, width, 0)
  ctx.fillText(`Dato for fotografering: ${ capture_date }`, width, font_size + spacing)

  const logo_height = logo_width / logo_image.naturalWidth * logo_image.naturalHeight || logo_height_fallback
  ctx.drawImage(logo_image, ((width / 2 ) - (logo_width / 2)), 0, logo_width, logo_height)

  return canvas
}

/**
 * Creates a PDF of the given map including a footer.
 * @param {Object} map The ol map to create a pdf of.
 * @param {Object} item The item that is shown on the map.
 * @param {function} callback The callback function called with the jsPDF as the first parameter and the file name as the second.
 * @param {number} resolution The resolution of the PDF.
 * @param {String} format The format of the PDF, supports a0 to a5.
 * @param {String} rotation The rotation of the PDF either 'landscape' or 'portrait'.
 */
function createPdf(map, item, callback, resolution=300, format='a4', rotation='landscape') {
  const dimension = dimensions[format]
  const resInch = resolution / mmPerInch
  const isLandscape = rotation === 'landscape'
  const canvas_width = Math.round(dimension[isLandscape ? 0 : 1] * resInch)
  const canvas_height = Math.round(dimension[isLandscape ? 1 : 0] * resInch)

  const image_max_height = canvas_height - footer_height - margin * 3
  const image_max_width = canvas_width - margin * 2

  const image_width = map.getViewport().offsetWidth
  const image_height = map.getViewport().offsetHeight

  const scale_factor = Math.min(image_max_width / image_width, image_max_height / image_height)

  const new_width = image_width * scale_factor
  const new_height = image_height * scale_factor
  const x = (image_max_width / 2) - (new_width / 2)
  const y = (image_max_height / 2) - (new_height / 2)

  const canvas = document.createElement('canvas')
  const ctx = canvas.getContext('2d')
  canvas.height = canvas_height
  canvas.width = canvas_width
  ctx.fillStyle = '#fff'
  ctx.fillRect(0, 0, canvas_width, canvas_height)

  // Find the viewport's canvas elements and draw their contont on top of each other
  const canvases = map.getViewport().querySelectorAll('canvas')
  canvases.forEach((canvas) => {
    ctx.drawImage(canvas, margin + x, margin + y, new_width, new_height)
  })

  // Draw footer
  ctx.drawImage(drawFooterContent(footer_height, image_max_width, item), margin, canvas_height - footer_height - margin)

  // Find tooltips
  let toolTips = []
  map.getViewport().querySelectorAll('[data-tooltip="measure"]').forEach(element => {
    const xy = [...element.parentNode.style.transform.matchAll(/\d+px/g)]
    toolTips.push({
      text: element.innerText,
      x: Number(xy[0][0].slice(0,-2)),
      y: Number(xy[1][0].slice(0,-2))
    })
  })
  // Draw tooltips in canvas
  const fontSize = 30
  toolTips.forEach(toolTip => {
    ctx.font = `${ fontSize }px Roboto`
    const textDimensions = ctx.measureText(toolTip.text)
    const coord = [
      (margin + x + toolTip.x * scale_factor),
      (margin + y + toolTip.y * scale_factor)
    ]
    ctx.fillStyle = 'hsl(198, 100%, 30%)'
    ctx.fillRect(coord[0], coord[1], (textDimensions.width + fontSize), fontSize * 1.5)
    ctx.fillStyle = 'white'
    ctx.textAlign = 'left'
    ctx.fillText(toolTip.text, (coord[0] + fontSize / 2), (coord[1] + fontSize))
  })

  // Generate the PDF
  const pdf = new jsPDF(rotation, undefined, format)
  pdf.addImage(canvas.toDataURL('image/jpeg'), 'JPEG', 0, 0, dimension[isLandscape ? 0 : 1], dimension[isLandscape ? 1 : 0])
  const file_name = generateFileName(item)
  callback(pdf, file_name)
}

export {
  createPdf
}