Move PDF editor out of plugin and into core

This commit is contained in:
Raphael Michel
2018-04-09 09:40:18 +02:00
parent f1d4a686b1
commit 87c54f07c6
19 changed files with 428 additions and 378 deletions

26834
src/pretix/static/fabric/fabric.js vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

9052
src/pretix/static/pdfjs/pdf.js vendored Normal file

File diff suppressed because it is too large Load Diff

50501
src/pretix/static/pdfjs/pdf.worker.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,727 @@
/*globals $, gettext, fabric, PDFJS*/
fabric.Barcodearea = fabric.util.createClass(fabric.Rect, {
type: 'barcodearea',
initialize: function (text, options) {
options || (options = {});
this.callSuper('initialize', text, options);
this.set('label', options.label || '');
},
toObject: function () {
return fabric.util.object.extend(this.callSuper('toObject'), {});
},
_render: function (ctx) {
this.callSuper('_render', ctx);
ctx.font = '16px Helvetica';
ctx.fillStyle = '#fff';
ctx.fillText(gettext('QR Code'), -this.width / 2, -this.height / 2 + 20);
},
});
fabric.Barcodearea.fromObject = function (object, callback, forceAsync) {
return fabric.Object._fromObject('Barcodearea', object, callback, forceAsync);
};
fabric.Textarea = fabric.util.createClass(fabric.Textbox, {
type: 'textarea',
initialize: function (text, options) {
options || (options = {});
this.callSuper('initialize', text, options);
this.set('content', options.content || '');
},
toObject: function(propertiesToInclude) {
return this.callSuper('toObject', ['content'].concat(propertiesToInclude));
}
});
fabric.Textarea.fromObject = function (object, callback, forceAsync) {
return fabric.Object._fromObject('Textarea', object, callback, forceAsync, 'text');
};
var editor = {
$pdfcv: null,
$fcv: null,
$cva: null,
$fabric: null,
objects: [],
history: [],
clipboard: [],
pdf_page: null,
pdf_scale: 1,
pdf_viewport: null,
_history_pos: 0,
_history_modification_in_progress: false,
dirty: false,
pdf_url: null,
uploaded_file_id: null,
_window_loaded: false,
_fabric_loaded: false,
_px2mm: function (v) {
return v / editor.pdf_scale / 72 * editor.pdf_page.userUnit * 25.4;
},
_mm2px: function (v) {
return v * editor.pdf_scale * 72 / editor.pdf_page.userUnit / 25.4;
},
_px2pt: function (v) {
return v / editor.pdf_scale * editor.pdf_page.userUnit;
},
_pt2px: function (v) {
return v * editor.pdf_scale / editor.pdf_page.userUnit;
},
dump: function (objs) {
var d = [];
objs = objs || editor.fabric.getObjects();
for (var i in objs) {
var o = objs[i];
var top = o.top;
var left = o.left;
if (o.group) {
top += o.group.top + o.group.height / 2;
left += o.group.left + o.group.width / 2;
}
if (o.type === "textarea") {
var col = (new fabric.Color(o.getFill()))._source;
d.push({
type: "textarea",
left: editor._px2mm(left).toFixed(2),
bottom: editor._px2mm(editor.pdf_viewport.height - o.height - top).toFixed(2),
fontsize: editor._px2pt(o.getFontSize()).toFixed(1),
color: col,
//lineheight: o.lineHeight,
fontfamily: o.fontFamily,
bold: o.fontWeight === 'bold',
italic: o.fontStyle === 'italic',
width: editor._px2mm(o.width).toFixed(2),
content: o.content,
text: o.text,
align: o.textAlign,
});
} else if (o.type === "barcodearea") {
d.push({
type: "barcodearea",
left: editor._px2mm(left).toFixed(2),
bottom: editor._px2mm(editor.pdf_viewport.height - o.height * o.scaleY - top).toFixed(2),
size: editor._px2mm(o.height * o.scaleY).toFixed(2)
});
}
}
return d;
},
_add_from_data: function (d) {
if (d.type === "barcodearea") {
o = editor._add_qrcode();
o.scaleToHeight(editor._mm2px(d.size));
} else if (d.type === "textarea" || o.type === "text") {
o = editor._add_text();
o.setColor('rgb(' + d.color[0] + ',' + d.color[1] + ',' + d.color[2] + ')');
o.setFontSize(editor._pt2px(d.fontsize));
//o.setLineHeight(d.lineheight);
o.setFontFamily(d.fontfamily);
o.setFontWeight(d.bold ? 'bold' : 'normal');
o.setFontStyle(d.italic ? 'italic' : 'normal');
o.setWidth(editor._mm2px(d.width));
o.content = d.content;
o.setTextAlign(d.align);
if (d.content === "other") {
o.setText(d.text);
} else {
o.setText(editor._get_text_sample(d.content));
}
}
var new_top = editor.pdf_viewport.height - editor._mm2px(d.bottom) - (o.height * o.scaleY);
o.set('left', editor._mm2px(d.left));
o.set('top', new_top);
o.setCoords();
return o;
},
load: function(data) {
editor.fabric.clear();
for (var i in data) {
var d = data[i], o;
editor._add_from_data(d);
}
editor.fabric.renderAll();
editor._update_toolbox_values();
},
_get_text_sample: function (key) {
if (key.startsWith('meta:')) {
return key.substr(5);
}
return $('#toolbox-content option[value='+key+']').attr('data-sample') || '';
},
_load_pdf: function (dump) {
// TODO: Loading indicators
var url = editor.pdf_url;
// TODO: Handle cross-origin issues if static files are on a different origin
PDFJS.workerSrc = editor.$pdfcv.attr("data-worker-url");
// Asynchronous download of PDF
var loadingTask = PDFJS.getDocument(url);
loadingTask.promise.then(function (pdf) {
console.log('PDF loaded');
// Fetch the first page
var pageNumber = 1;
pdf.getPage(pageNumber).then(function (page) {
console.log('Page loaded');
var canvas = document.getElementById('pdf-canvas');
var scale = editor.$cva.width() / page.getViewport(1.0).width;
var viewport = page.getViewport(scale);
// Prepare canvas using PDF page dimensions
var context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
canvas.height = viewport.height;
canvas.width = viewport.width;
editor.pdf_page = page;
editor.pdf_scale = scale;
editor.pdf_viewport = viewport;
// Render PDF page into canvas context
var renderContext = {
canvasContext: context,
viewport: viewport
};
var renderTask = page.render(renderContext);
renderTask.then(function () {
console.log('Page rendered');
editor._init_fabric(dump);
});
});
}, function (reason) {
var msg = gettext('The PDF background file could not be loaded for the following reason:');
editor._error(msg + ' ' + reason);
});
},
_init_fabric: function (dump) {
editor.$fcv.get(0).width = editor.$pdfcv.get(0).width;
editor.$fcv.get(0).height = editor.$pdfcv.get(0).height;
editor.fabric = new fabric.Canvas('fabric-canvas');
editor.fabric.on('object:modified', editor._create_savepoint);
editor.fabric.on('object:added', editor._create_savepoint);
editor.fabric.on('selection:cleared', editor._update_toolbox);
editor.fabric.on('selection:created', editor._update_toolbox);
editor.fabric.on('object:selected', editor._update_toolbox);
editor.fabric.on('object:moving', editor._update_toolbox_values);
editor.fabric.on('object:modified', editor._update_toolbox_values);
editor.fabric.on('object:scaling', editor._update_toolbox_values);
editor._update_toolbox();
$("#toolbox-content-other").hide();
$(".add-buttons button").prop('disabled', false);
if (dump) {
editor.load(dump);
} else {
var data = $.trim($("#editor-data").text());
if (data) {
editor.load(JSON.parse(data));
}
}
editor.history = [];
editor._create_savepoint();
editor.dirty = !!dump;
if ($("#loading-upload").is(":visible")) {
$("#loading-container, #loading-upload").hide();
}
editor._fabric_loaded = true;
console.log("Fabric loaded");
if (editor._window_loaded) {
editor._ready();
}
},
_window_load_event: function () {
editor._window_loaded = true;
console.log("Window loaded");
if (editor._fabric_loaded) {
editor._ready();
}
},
_ready: function () {
$("#editor-loading").hide();
$("#editor-start").removeClass("sr-only");
$("#editor-start").click(function () {
$("#loading-container").hide();
$("#loading-initial").remove();
});
},
_update_toolbox_values: function () {
var o = editor.fabric.getActiveObject();
if (!o) {
o = editor.fabric.getActiveGroup();
if (!o) {
return;
}
}
$("#toolbox-position-x").val(editor._px2mm(o.left).toFixed(2));
$("#toolbox-position-y").val(editor._px2mm(editor.pdf_viewport.height - o.height * o.scaleY - o.top).toFixed(2));
if (o.type === "barcodearea") {
$("#toolbox-squaresize").val(editor._px2mm(o.height * o.scaleY).toFixed(2));
} else if (o.type === "text" || o.type === "textarea") {
var col = (new fabric.Color(o.getFill()))._source;
$("#toolbox-col").val("#" + ((1 << 24) + (col[0] << 16) + (col[1] << 8) + col[2]).toString(16).slice(1));
$("#toolbox-fontsize").val(editor._px2pt(o.fontSize).toFixed(1));
//$("#toolbox-lineheight").val(o.lineHeight);
$("#toolbox-fontfamily").val(o.fontFamily);
$("#toolbox").find("button[data-action=bold]").toggleClass('active', o.fontWeight === 'bold');
$("#toolbox").find("button[data-action=italic]").toggleClass('active', o.fontStyle === 'italic');
$("#toolbox").find("button[data-action=left]").toggleClass('active', o.textAlign === 'left');
$("#toolbox").find("button[data-action=center]").toggleClass('active', o.textAlign === 'center');
$("#toolbox").find("button[data-action=right]").toggleClass('active', o.textAlign === 'right');
$("#toolbox-textwidth").val(editor._px2mm(o.width).toFixed(2));
if (o.type === "textarea") {
$("#toolbox-content").val(o.content);
$("#toolbox-content-other").toggle($("#toolbox-content").val() === "other");
if (o.content === "other") {
$("#toolbox-content-other").val(o.text);
} else {
$("#toolbox-content-other").val("");
}
}
}
},
_update_values_from_toolbox: function () {
var o = editor.fabric.getActiveObject();
if (!o) {
o = editor.fabric.getActiveGroup();
if (!o) {
return;
}
}
var new_top = editor.pdf_viewport.height - editor._mm2px($("#toolbox-position-y").val()) - o.height * o.scaleY;
o.set('left', editor._mm2px($("#toolbox-position-x").val()));
o.set('top', new_top);
if (o.type === "barcodearea") {
var new_h = editor._mm2px($("#toolbox-squaresize").val());
new_top += o.height * o.scaleY - new_h;
o.setHeight(new_h);
o.setWidth(new_h);
o.setScaleX(1);
o.setScaleY(1);
o.set('top', new_top)
} else if (o.type === "textarea" || o.type === "text") {
o.setColor($("#toolbox-col").val());
o.setFontSize(editor._pt2px($("#toolbox-fontsize").val()));
//o.setLineHeight($("#toolbox-lineheight").val());
o.setFontFamily($("#toolbox-fontfamily").val());
o.setFontWeight($("#toolbox").find("button[data-action=bold]").is('.active') ? 'bold' : 'normal');
o.setFontStyle($("#toolbox").find("button[data-action=italic]").is('.active') ? 'italic' : 'normal');
var align = $("#toolbox-align").find(".active").attr("data-action");
if (align) {
o.setTextAlign(align);
}
o.setWidth(editor._mm2px($("#toolbox-textwidth").val()));
$("#toolbox-content-other").toggle($("#toolbox-content").val() === "other");
o.content = $("#toolbox-content").val();
if ($("#toolbox-content").val() === "other") {
o.setText($("#toolbox-content-other").val());
} else {
o.setText(editor._get_text_sample($("#toolbox-content").val()));
}
}
o.setCoords();
editor.fabric.renderAll();
},
_update_toolbox: function () {
if (editor.fabric.getActiveGroup()) {
$("#toolbox").attr("data-type", "group");
$("#toolbox-heading").text(gettext("Group of objects"));
var g = editor.fabric.getActiveGroup();
} else if (editor.fabric.getActiveObject()) {
var o = editor.fabric.getActiveObject();
$("#toolbox").attr("data-type", o.type);
if (o.type === "textarea" || o.type === "text") {
$("#toolbox-heading").text(gettext("Text object"));
} else if (o.type === "barcodearea") {
$("#toolbox-heading").text(gettext("Barcode area"));
} else {
$("#toolbox-heading").text(gettext("Object"));
}
} else {
$("#toolbox").removeAttr("data-type");
$("#toolbox-heading").text(gettext("Ticket design"));
$("#pdf-info-width").val(editor._px2mm(editor.pdf_viewport.width).toFixed(2));
$("#pdf-info-height").val(editor._px2mm(editor.pdf_viewport.height).toFixed(2));
}
editor._update_toolbox_values();
},
_error: function (msg) {
editor.$cva.before("<div class='alert alert-danger'>" + msg + "</div>");
},
_add_text: function () {
var text = new fabric.Textarea(editor._get_text_sample('event_name'), {
left: 100,
top: 100,
width: editor._mm2px(50),
lockRotation: true,
fontFamily: 'Open Sans',
lineHeight: 1,
content: 'item',
editable: false,
fontSize: editor._pt2px(13)
});
text.setControlsVisibility({
'tr': false,
'tl': false,
'mt': false,
'br': false,
'bl': false,
'mb': false,
'mr': true,
'ml': true,
'mtr': false
});
editor.fabric.add(text);
editor._create_savepoint();
return text;
},
_add_qrcode: function () {
var rect = new fabric.Barcodearea({
left: 100,
top: 100,
width: 100,
height: 100,
lockRotation: true,
lockUniScaling: true,
fill: '#666',
});
rect.setControlsVisibility({'mtr': false});
editor.fabric.add(rect);
editor._create_savepoint();
return rect;
},
_cut: function () {
editor._history_modification_in_progress = true;
var thing = editor.fabric.getActiveObject() ? editor.fabric.getActiveObject() : editor.fabric.getActiveGroup();
if (thing.type === "group") {
editor.clipboard = editor.dump(thing._objects);
thing.forEachObject(function (o) {
o.remove();
});
thing.remove();
} else {
editor.clipboard = editor.dump([thing]);
thing.remove();
}
editor.fabric.discardActiveGroup();
editor.fabric.discardActiveObject();
editor._history_modification_in_progress = false;
editor._create_savepoint();
},
_copy: function () {
editor._history_modification_in_progress = true;
var thing = editor.fabric.getActiveObject() ? editor.fabric.getActiveObject() : editor.fabric.getActiveGroup();
if (thing.type === "group") {
editor.clipboard = editor.dump(thing._objects);
} else {
editor.clipboard = editor.dump([thing]);
}
editor._history_modification_in_progress = false;
editor._create_savepoint();
},
_paste: function () {
if (editor.clipboard.length < 1) {
return;
}
editor._history_modification_in_progress = true;
var objs = [];
for (var i in editor.clipboard) {
objs.push(editor._add_from_data(editor.clipboard[i]));
}
editor.fabric.discardActiveObject();
editor.fabric.discardActiveGroup();
if (editor.clipboard.length > 1) {
var group = new fabric.Group(objs, {
originX: 'left',
originY: 'top',
left: 100,
top: 100,
});
group.setCoords();
editor.fabric.setActiveGroup(group);
} else {
editor.fabric.setActiveObject(objs[0]);
}
editor._history_modification_in_progress = false;
editor._create_savepoint();
},
_delete: function () {
var thing = editor.fabric.getActiveObject() ? editor.fabric.getActiveObject() : editor.fabric.getActiveGroup();
if (thing.type === "group") {
thing.forEachObject(function (o) {
o.remove();
});
thing.remove();
editor.fabric.discardActiveGroup();
} else {
thing.remove();
editor.fabric.discardActiveObject();
}
editor._create_savepoint();
},
_on_keydown: function (e) {
var step = e.shiftKey ? editor._mm2px(10) : editor._mm2px(1);
var thing = editor.fabric.getActiveObject() ? editor.fabric.getActiveObject() : editor.fabric.getActiveGroup();
switch (e.keyCode) {
case 38: /* Up arrow */
thing.set('top', thing.get('top') - step);
thing.setCoords();
editor._create_savepoint();
break;
case 40: /* Down arrow */
thing.set('top', thing.get('top') + step);
thing.setCoords();
editor._create_savepoint();
break;
case 37: /* Left arrow */
thing.set('left', thing.get('left') - step);
thing.setCoords();
editor._create_savepoint();
break;
case 39: /* Right arrow */
thing.set('left', thing.get('left') + step);
thing.setCoords();
editor._create_savepoint();
break;
case 46: /* Delete */
editor._delete();
break;
case 89: /* Y */
if (e.ctrlKey) {
editor._redo();
}
break;
case 90: /* Z */
if (e.ctrlKey) {
editor._undo();
}
break;
case 88: /* X */
if (e.ctrlKey) {
editor._cut();
}
break;
case 86: /* V */
if (e.ctrlKey) {
editor._paste();
}
break;
case 67: /* C */
if (e.ctrlKey) {
editor._copy();
}
break;
default:
return;
}
e.preventDefault();
editor.fabric.renderAll();
editor._update_toolbox_values();
},
_create_savepoint: function () {
if (editor._history_modification_in_progress) {
return;
}
var state = editor.dump();
if (editor._history_pos > 0) {
editor.history.splice(-1 * editor._history_pos, editor._history_pos);
editor._history_pos = 0;
}
editor.history.push(state);
editor.dirty = true;
},
_undo: function undo() {
if (editor._history_pos < editor.history.length - 1) {
editor._history_modification_in_progress = true;
editor._history_pos += 1;
editor.fabric.clear().renderAll();
editor.load(editor.history[editor.history.length - 1 - editor._history_pos]);
editor._history_modification_in_progress = false;
editor.dirty = true;
}
},
_redo: function redo() {
if (editor._history_pos > 0) {
editor._history_modification_in_progress = true;
editor._history_pos -= 1;
editor.load(editor.history[editor.history.length - 1 - editor._history_pos]);
editor._history_modification_in_progress = false;
editor.dirty = true;
}
},
_save: function () {
$("#editor-save").prop('disabled', true).prepend('<span class="fa fa-cog fa-spin"></span>');
var dump = editor.dump();
$.post(window.location.href, {
'data': JSON.stringify(dump),
'csrfmiddlewaretoken': $("input[name=csrfmiddlewaretoken]").val(),
'background': editor.uploaded_file_id,
}, function (data) {
if (data.status === 'ok') {
$("#editor-save span").remove();
$("#editor-save").prop('disabled', false);
editor.dirty = false;
editor.uploaded_file_id = null;
} else {
alert(gettext('Saving failed.'));
}
}, 'json');
return false;
},
_preview: function () {
$("#preview-form input[name=data]").val(JSON.stringify(editor.dump()));
$("#preview-form input[name=background]").val(editor.uploaded_file_id);
$("#preview-form").get(0).submit();
},
_replace_pdf_file: function (url) {
editor.pdf_url = url;
d = editor.dump();
editor.fabric.dispose();
editor._load_pdf(d);
},
_source_show: function () {
$("#source-textarea").text(JSON.stringify(editor.dump()));
$("#source-container").show();
},
_source_close: function () {
$("#source-container").hide();
},
_source_save: function () {
editor.load(JSON.parse($("#source-textarea").val()));
$("#source-container").hide();
},
init: function () {
editor.$pdfcv = $("#pdf-canvas");
editor.pdf_url = editor.$pdfcv.attr("data-pdf-url");
editor.$fcv = $("#fabric-canvas");
editor.$cva = $("#editor-canvas-area");
editor._load_pdf();
$("#editor-add-qrcode").click(editor._add_qrcode);
$("#editor-add-text").click(editor._add_text);
editor.$cva.get(0).tabIndex = 1000;
editor.$cva.on("keydown", editor._on_keydown);
$("#editor-save").on("click", editor._save);
$("#editor-preview").on("click", editor._preview);
window.onbeforeunload = function () {
if (editor.dirty) {
return gettext("Do you really want to leave the editor without saving your changes?");
}
};
$("#source-container").hide();
$('#fileupload').fileupload({
url: location.href,
dataType: 'json',
done: function (e, data) {
if (data.result.status === "ok") {
editor.uploaded_file_id = data.result.id;
editor._replace_pdf_file(data.result.url);
} else {
alert(data.result.error || gettext("Error while uploading your PDF file, please try again."));
$("#loading-container, #loading-upload").hide();
}
$("#fileupload").prop('disabled', false);
$(".fileinput-button").removeClass("disabled");
},
add: function (e, data) {
data.formData = {
'csrfmiddlewaretoken': $("input[name=csrfmiddlewaretoken]").val()
};
$("#loading-container, #loading-upload").show();
$("#loading-upload .progress").show();
$('#loading-upload .progress-bar').css('width', 0);
$("#fileupload").prop('disabled', true);
$(".fileinput-button").addClass("disabled");
data.process().done(function () {
data.submit();
});
},
progressall: function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
$('#loading-upload .progress-bar').css('width', progress + '%');
}
}).prop('disabled', !$.support.fileInput).parent().addClass($.support.fileInput ? undefined : 'disabled');
$("#toolbox input[type=number], #toolbox textarea, #toolbox input[type=text]").bind('change keydown keyup' +
' input', editor._update_values_from_toolbox);
$("#toolbox input[type=number], #toolbox textarea, #toolbox input[type=text], #toolbox input[type=radio]").bind('change', editor._create_savepoint);
$("#toolbox label.btn").bind('click change', editor._update_values_from_toolbox);
$("#toolbox select").bind('change', editor._update_values_from_toolbox);
$("#toolbox select").bind('change', editor._create_savepoint);
$("#toolbox button.toggling").bind('click change', function () {
if ($(this).is(".option")) {
$(this).addClass("active");
$(this).parent().siblings().find("button").removeClass("active");
} else {
$(this).toggleClass("active");
}
editor._update_values_from_toolbox();
editor._create_savepoint();
});
$("#toolbox .colorpickerfield").bind('changeColor', editor._update_values_from_toolbox);
$("#toolbox-copy").bind('click', editor._copy);
$("#toolbox-cut").bind('click', editor._cut);
$("#toolbox-delete").bind('click', editor._delete);
$("#toolbox-paste").bind('click', editor._paste);
$("#toolbox-undo").bind('click', editor._undo);
$("#toolbox-redo").bind('click', editor._redo);
$("#toolbox-source").bind('click', editor._source_show);
$("#source-close").bind('click', editor._source_close);
$("#source-save").bind('click', editor._source_save);
}
};
$(function () {
editor.init();
});
$(window).bind('load', editor._window_load_event);

View File

@@ -0,0 +1,54 @@
#fabric-container {
position: absolute;
top: 0;
left: 0;
}
#editor-canvas-area {
position: relative;
min-height: 250px;
}
body {
overflow-y: scroll;
}
#toolbox .control-group {
margin-bottom: 5px;
}
#toolbox .position, #toolbox .squaresize, #toolbox[data-type] .pdf-info, #toolbox .text, #toolbox .object-buttons {
display: none;
}
#toolbox[data-type] .position, #toolbox[data-type=barcodearea] .squaresize, #toolbox[data-type=text] .text, #toolbox[data-type=textarea] .text,
#toolbox[data-type] .object-buttons {
display: block;
}
#loading-container {
position: absolute;
top: 0;
left: 0;
height: 100%;
background: white;
width: 100%;
text-align: center;
}
#loading-container > div {
max-width: 600px;
margin: auto;
}
#loading-upload {
display: none;
}
.preload-font {
visibility: hidden;
}
#source-container {
position: absolute;
top: 0;
left: 0;
height: 100%;
background: white;
width: 100%;
text-align: center;
}
#source-container textarea {
width: 100%;
height: 250px;
}