// ==UserScript==
// @name Chefkoch PDF export
// @description Erzeugt aus einem Rezept ein PDF Dokument zum Herunterladen oder Drucken
// @namespace cuzi
// @oujs:author cuzi
// @version 1
// @include http://www.chefkoch.de/rezepte/*
// @grant GM_xmlhttpRequest
// @require https://greasyfork.org/scripts/15924-jspdf/code/jsPDF.js
// ==/UserScript==
function convertImgToDataURLviaCanvas(url, outputFormat, callback){
var img = new Image();
img.crossOrigin = 'Anonymous';
img.onload = function(){
var canvas = document.createElement('CANVAS');
var ctx = canvas.getContext('2d');
var dataURL;
canvas.height = this.height;
canvas.width = this.width;
ctx.drawImage(this, 0, 0);
dataURL = canvas.toDataURL(outputFormat);
callback(dataURL, this.height, this.width);
canvas = null;
};
img.src = url;
}
function trimArray(arr) {
return arr.map((e) => e.trim());
}
function trimMultiline(s) {
return trimArray(s.split("\n")).join("\n").trim();
}
function splitText(doc, s, size) {
size = size?size:500;
var p = s.split("\n");
var r = [];
for(var i = 0; i < p.length; i++) {
var t = p[i].trim();
if(t) {
r.push(t);
}
}
s = r.join("\n").trim();
return doc.splitTextToSize(s, size);
}
function makeColums(doc, x, y, fontSize, columnSep, rowSep, data) {
/* Write text to pdf in columns
data = [
[text1, text2, text2],
[text3, text4, text5],
....
]
*/
doc.setFontSize(fontSize);
var columnWidth = [];
for(var i = 0; i < data.length; i++) {
for(var j = 0; j < data[i].length; j++) {
var textWidth = doc.getStringUnitWidth(data[i][j]);
if(columnWidth[j]) {
columnWidth[j] = Math.max(columnWidth[j], fontSize * textWidth);
} else {
columnWidth.push(fontSize * textWidth);
}
}
}
var start_x = x;
for(var i = 0; i < data.length; i++) {
for(var j = 0; j < data[i].length; j++) {
doc.text(x, y, data[i][j]);
x += columnWidth[j] + columnSep;
}
x = start_x;
y += doc.getLineHeight() + rowSep;
}
// Return total width and height
var total_width = columnWidth.length * Math.max(...columnWidth) + columnWidth.length * columnSep;
var total_height = data.length * doc.getLineHeight() + data.length * rowSep;
return [total_width, total_height];
}
function Layout(doc, x, y) {
var lineSep = 0;
var width = 500;
var start_x = x;
var start_y = y;
this.move = function move(toX,toY) {
x = toX;
y = toY;
};
this.pos = function pos() {
return {"x" : x, "y" : y};
};
this.pageWidth = function pageWidth() {
return width;
};
this.setLineSep = function setLineSep(newlineSep) {
lineSep = newlineSep
return this;
};
this.text = function text(fontSize, str) {
doc.setFontSize(fontSize);
doc.text(x, y, doc.splitTextToSize(str, width-x));
x += doc.getStringUnitWidth(str) * fontSize;
return this;
};
this.line = function line(fontSize, str) {
doc.setFontSize(fontSize);
x = start_x;
var textHeight = doc.splitTextToSize(str, width-x).length * doc.getLineHeight();
this.text(fontSize, str);
y += textHeight + lineSep;
return this;
};
this.r = function r() {
x = start_x;
};
this.br = function br(numberOfNewLines) {
if(numberOfNewLines === -1) {
x = start_x;
y -= doc.getLineHeight() - lineSep;
} else if(numberOfNewLines > 1) {
for(var i = 0; i < numberOfNewLines; i ++) {
br(1);
}
} else if(numberOfNewLines < 0) {
for(var i = 0; i < -numberOfNewLines; i ++) {
br(-1);
}
} else {
x = start_x;
y += doc.getLineHeight() + lineSep;
}
return this;
};
this.columns = function columns(fontSize, columnSep, rowSep, data) {
var res = makeColums(doc, x, y, fontSize, columnSep, rowSep, data);
x += res[0];
y += res[1] + lineSep;
return this;
};
}
function RecipePage() {
if(!document.querySelector("#recipe-incredients")) {
throw Error("RecipePage() needs a recipe page");
}
this.title = function getTitle() {
return document.querySelector("h1").textContent.trim();
};
this.summary = function getSummary() {
return document.querySelector(".summary")?document.querySelector(".summary").textContent.trim():"";
};
this.ingredients = function getIngredients() {
var ingredients = [];
var tr = document.querySelectorAll("#recipe-incredients tr");
for(var i = 0; i < tr.length; i++) {
var td = tr[i].getElementsByTagName("td");
var c = [];
for(var j = 0; j < td.length; j++) {
c.push(td[j].textContent.trim());
}
if(c) {
ingredients.push(c);
}
}
return ingredients;
};
this.servings = function getServings() {
return (document.querySelector("#divisor").value + ' ' + document.querySelector("#divisor").nextElementSibling.firstChild.data).trim();
};
this.details = function getDetails() {
return trimMultiline(document.querySelector("#rezept-zubereitung").previousElementSibling.textContent.trim().replace(/(\s)\s*/g,"$1").replace(/\n/g," "));
};
this.instructions = function getInstructions() {
return trimMultiline(document.querySelector("#rezept-zubereitung").textContent);
};
this.imageURL = function getImageURL() {
if(document.querySelectorAll("#slideshow a")[0].href) {
return Array.from(document.querySelectorAll("#slideshow a")).filter((e) => e.style.display == 'block')[0].href.toString();
} else {
return false;
}
};
}
function makePdf(cb, recipe, imageData, imgWidth, imgHeight) {
var doc = new jsPDF("portrait", 'pt', 'a4');
var layout = new Layout(doc, 20, 20);
layout.setLineSep(5);
layout.line(14,recipe.title());
layout.line(11, recipe.summary());
var image_y_start = layout.pos().y;
layout.br();
layout.line(13, "Zutaten (für "+recipe.servings()+")").r();
layout.columns(12, 20, 5, recipe.ingredients());
var image_x_start = layout.pos().x;
var image_y_end = layout.pos().y;
layout.line(13, "Zubereitung");
layout.line(12, recipe.details());
layout.line(12, recipe.instructions());
if(imageData) {
var newImageWidth = layout.pageWidth() - image_x_start;
var newImageHeight = image_y_end - image_y_start;
var scale = Math.min(newImageWidth/imgWidth, newImageHeight/imgHeight);
newImageWidth = Math.ceil(scale * imgWidth);
newImageHeight = Math.ceil(scale * imgHeight);
doc.addImage(imageData, 'JPEG', image_x_start, image_y_start, newImageHeight, newImageWidth);
}
var datauristring = doc.output('datauristring');
var div = document.createElement("div");
div.style = "background:#90b262; position:absolute; top:15px; left:2px; padding:3px 5px;";
document.body.appendChild(div);
var head = document.createElement("div");
head.style = "color:White; height:30px;";
head.appendChild(document.createTextNode("PDF Dokument: "));
var a = document.createElement("a");
a.style = "margin-left:10px; color:white; text-decoration:underline";
a.href = datauristring;
a.target = '_blank';
a.appendChild(document.createTextNode("Download"));
head.appendChild(a);
var close = document.createElement("a");
head.appendChild(close);
close.innerHTML = '<button id="cboxClose" style="top: -15px;" type="button"><span>x</span></button>';
close.style = "cursor:pointer;";
close.addEventListener("click",function() {document.body.removeChild(div);});
div.appendChild(head);
var iframe = document.createElement("iframe");
iframe.style = "width:400px; height:600px; ";
div.appendChild(iframe);
iframe.src = datauristring;
cb(doc);
}
function downloadImageAndMakePdf(cb) {
var recipe = new RecipePage();
if(recipe.imageURL()) {
convertImgToDataURLviaCanvas(recipe.imageURL(), "jpeg", function(dataURI, width, height) {
makePdf(cb, recipe, dataURI, width, height);
});
} else {
makePdf(cb, recipe, false);
}
};
(function () {
var a = document.querySelector("#recipe-buttons a").cloneNode();
a.innerHTML = "PDF";
a.href = "javascript:void(0)";
a.title = "PDF Dokument erzeugen";
a.className = "button-green button-file-export";
var click = function() {
a.innerHTML = "Warten auf PDF...";
window.setTimeout(function() {
downloadImageAndMakePdf(function(doc) {
window.setTimeout(function() {
a.innerHTML = "PDF erstellt.";
a.title = "Hier klicken um PDF zu öffnen. Rechtsklick zum Speichern.";
a.removeEventListener("click", click);
a.href = doc.output('datauristring');
}, 5000);
});
},1);
};
a.addEventListener("click", click);
document.querySelector("#recipe-buttons").insertBefore(a, document.querySelector("#recipe-buttons a"));
})();