diff --git a/debian/proxmox-backup-docs.links b/debian/proxmox-backup-docs.links
index 719dd555..739115fa 100644
--- a/debian/proxmox-backup-docs.links
+++ b/debian/proxmox-backup-docs.links
@@ -1,2 +1,3 @@
/usr/share/doc/proxmox-backup/proxmox-backup.pdf /usr/share/doc/proxmox-backup/html/proxmox-backup.pdf
/usr/share/javascript/extjs /usr/share/doc/proxmox-backup/html/prune-simulator/extjs
+/usr/share/javascript/extjs /usr/share/doc/proxmox-backup/html/lto-barcode/extjs
diff --git a/docs/Makefile b/docs/Makefile
index 4c1d2b26..a6e8c988 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -20,6 +20,19 @@ PRUNE_SIMULATOR_FILES := \
prune-simulator/clear-trigger.png \
prune-simulator/prune-simulator.js
+LTO_BARCODE_FILES := \
+ lto-barcode/index.html \
+ lto-barcode/code39.js \
+ lto-barcode/prefix-field.js \
+ lto-barcode/label-style.js \
+ lto-barcode/tape-type.js \
+ lto-barcode/paper-size.js \
+ lto-barcode/page-layout.js \
+ lto-barcode/page-calibration.js \
+ lto-barcode/label-list.js \
+ lto-barcode/label-setup.js \
+ lto-barcode/lto-barcode.js
+
# Sphinx documentation setup
SPHINXOPTS =
SPHINXBUILD = sphinx-build
@@ -79,11 +92,13 @@ onlinehelpinfo:
@echo "Build finished. OnlineHelpInfo.js is in $(BUILDDIR)/scanrefs."
.PHONY: html
-html: ${GENERATED_SYNOPSIS} images/proxmox-logo.svg custom.css conf.py ${PRUNE_SIMULATOR_FILES}
+html: ${GENERATED_SYNOPSIS} images/proxmox-logo.svg custom.css conf.py ${PRUNE_SIMULATOR_FILES} ${LTO_BARCODE_FILES}
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
install -m 0644 custom.js custom.css images/proxmox-logo.svg $(BUILDDIR)/html/_static/
install -dm 0755 $(BUILDDIR)/html/prune-simulator
install -m 0644 ${PRUNE_SIMULATOR_FILES} $(BUILDDIR)/html/prune-simulator
+ install -dm 0755 $(BUILDDIR)/html/lto-barcode
+ install -m 0644 ${LTO_BARCODE_FILES} $(BUILDDIR)/html/lto-barcode
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
diff --git a/docs/conf.py b/docs/conf.py
index 0e741124..45497d85 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -172,6 +172,7 @@ html_theme_options = {
'Proxmox Homepage': 'https://proxmox.com',
'PDF': 'proxmox-backup.pdf',
'Prune Simulator' : 'prune-simulator/index.html',
+ 'LTO Barcode Generator' : 'lto-barcode/index.html',
},
'sidebar_width': '320px',
diff --git a/docs/lto-barcode/code39.js b/docs/lto-barcode/code39.js
new file mode 100644
index 00000000..d17c2ece
--- /dev/null
+++ b/docs/lto-barcode/code39.js
@@ -0,0 +1,351 @@
+// Code39 barcode generator
+// see https://en.wikipedia.org/wiki/Code_39
+
+// IBM LTO Ultrium Cartridge Label Specification
+// http://www-01.ibm.com/support/docview.wss?uid=ssg1S7000429
+
+let code39_codes = {
+ "1": ['B', 's', 'b', 'S', 'b', 's', 'b', 's', 'B'],
+ "A": ['B', 's', 'b', 's', 'b', 'S', 'b', 's', 'B'],
+ "K": ['B', 's', 'b', 's', 'b', 's', 'b', 'S', 'B'],
+ "U": ['B', 'S', 'b', 's', 'b', 's', 'b', 's', 'B'],
+
+ "2": ['b', 's', 'B', 'S', 'b', 's', 'b', 's', 'B'],
+ "B": ['b', 's', 'B', 's', 'b', 'S', 'b', 's', 'B'],
+ "L": ['b', 's', 'B', 's', 'b', 's', 'b', 'S', 'B'],
+ "V": ['b', 'S', 'B', 's', 'b', 's', 'b', 's', 'B'],
+
+ "3": ['B', 's', 'B', 'S', 'b', 's', 'b', 's', 'b'],
+ "C": ['B', 's', 'B', 's', 'b', 'S', 'b', 's', 'b'],
+ "M": ['B', 's', 'B', 's', 'b', 's', 'b', 'S', 'b'],
+ "W": ['B', 'S', 'B', 's', 'b', 's', 'b', 's', 'b'],
+
+ "4": ['b', 's', 'b', 'S', 'B', 's', 'b', 's', 'B'],
+ "D": ['b', 's', 'b', 's', 'B', 'S', 'b', 's', 'B'],
+ "N": ['b', 's', 'b', 's', 'B', 's', 'b', 'S', 'B'],
+ "X": ['b', 'S', 'b', 's', 'B', 's', 'b', 's', 'B'],
+
+ "5": ['B', 's', 'b', 'S', 'B', 's', 'b', 's', 'b'],
+ "E": ['B', 's', 'b', 's', 'B', 'S', 'b', 's', 'b'],
+ "O": ['B', 's', 'b', 's', 'B', 's', 'b', 'S', 'b'],
+ "Y": ['B', 'S', 'b', 's', 'B', 's', 'b', 's', 'b'],
+
+ "6": ['b', 's', 'B', 'S', 'B', 's', 'b', 's', 'b'],
+ "F": ['b', 's', 'B', 's', 'B', 'S', 'b', 's', 'b'],
+ "P": ['b', 's', 'B', 's', 'B', 's', 'b', 'S', 'b'],
+ "Z": ['b', 'S', 'B', 's', 'B', 's', 'b', 's', 'b'],
+
+ "7": ['b', 's', 'b', 'S', 'b', 's', 'B', 's', 'B'],
+ "G": ['b', 's', 'b', 's', 'b', 'S', 'B', 's', 'B'],
+ "Q": ['b', 's', 'b', 's', 'b', 's', 'B', 'S', 'B'],
+ "-": ['b', 'S', 'b', 's', 'b', 's', 'B', 's', 'B'],
+
+ "8": ['B', 's', 'b', 'S', 'b', 's', 'B', 's', 'b'],
+ "H": ['B', 's', 'b', 's', 'b', 'S', 'B', 's', 'b'],
+ "R": ['B', 's', 'b', 's', 'b', 's', 'B', 'S', 'b'],
+ ".": ['B', 'S', 'b', 's', 'b', 's', 'B', 's', 'b'],
+
+ "9": ['b', 's', 'B', 'S', 'b', 's', 'B', 's', 'b'],
+ "I": ['b', 's', 'B', 's', 'b', 'S', 'B', 's', 'b'],
+ "S": ['b', 's', 'B', 's', 'b', 's', 'B', 'S', 'b'],
+ " ": ['b', 'S', 'B', 's', 'b', 's', 'B', 's', 'b'],
+
+ "0": ['b', 's', 'b', 'S', 'B', 's', 'B', 's', 'b'],
+ "J": ['b', 's', 'b', 's', 'B', 'S', 'B', 's', 'b'],
+ "T": ['b', 's', 'b', 's', 'B', 's', 'B', 'S', 'b'],
+ "*": ['b', 'S', 'b', 's', 'B', 's', 'B', 's', 'b']
+};
+
+let colors = [
+ '#BB282E',
+ '#FAE54A',
+ '#9AC653',
+ '#01A5E2',
+ '#9EAAB6',
+ '#D97E35',
+ '#E27B99',
+ '#67A945',
+ '#F6B855',
+ '#705A81'
+];
+
+let lto_label_width = 70;
+let lto_label_height = 17;
+
+function foreach_label(page_layout, callback) {
+
+ let count = 0;
+ let row = 0;
+ let height = page_layout.margin_top;
+
+ while ((height + page_layout.label_height) <= page_layout.page_height) {
+
+ let column = 0;
+ let width = page_layout.margin_left;
+
+ while ((width + page_layout.label_width) <= page_layout.page_width) {
+
+ callback(column, row, count, width, height);
+ count += 1;
+
+ column += 1;
+ width += page_layout.label_width;
+ width += page_layout.column_spacing;
+ }
+
+ row += 1;
+ height += page_layout.label_height;
+ height += page_layout.row_spacing;
+ }
+
+}
+
+function compute_max_labels(page_layout) {
+
+ let max_labels = 0;
+ foreach_label(page_layout, function() { max_labels += 1; });
+ return max_labels;
+}
+
+function svg_label(mode, label, label_type, pagex, pagey, label_borders) {
+ let svg = "";
+
+ if (label.length != 6) {
+ throw "wrong label length";
+ }
+ if (label_type.length != 2) {
+ throw "wrong label_type length";
+ }
+
+ let ratio = 2.75;
+ let parts = 3*ratio + 6; // 3*wide + 6*small;
+ let barcode_width = (lto_label_width/12)*10; // 10*code + 2margin
+ let small = barcode_width/(parts*10 + 9);
+ let code_width = small*parts;
+ let wide = small*ratio;
+ let xpos = pagex + code_width;
+ let height = 12;
+
+ if (mode === 'placeholder') {
+ if (label_borders) {
+ svg += ``;
+ }
+ return svg;
+ }
+ if (label_borders) {
+ svg += ``;
+ }
+
+ if (mode === "color" || mode == "frame") {
+ let w = lto_label_width/8;
+ let h = lto_label_height - height;
+ for (var i = 0; i < 7; i++) {
+ let textx = w/2 + pagex + i*w;
+ let texty = pagey;
+
+ let fill = "none";
+ if (mode === "color" && (i < 6)) {
+ let letter = label.charAt(i);
+ if (letter >= '0' && letter <= '9') {
+ fill = colors[parseInt(letter, 10)];
+ }
+ }
+
+ svg += ``;
+
+ if (i == 6) {
+ textx += 3;
+ texty += 3.7;
+ svg += `${label_type}`;
+ } else {
+ let letter = label.charAt(i);
+ textx += 3.5;
+ texty += 4;
+ svg += `${letter}`;
+ }
+ }
+ }
+
+ let raw_label = `*${label}${label_type}*`;
+
+ for (var i = 0; i < raw_label.length; i++) {
+ let letter = raw_label.charAt(i);
+
+ let code = code39_codes[letter];
+ if (code === undefined) {
+ throw `unable to encode letter '${letter}' with code39`;
+ }
+
+ if (mode === "simple") {
+ let textx = xpos + code_width/2;
+ let texty = pagey + 4;
+
+ if (i > 0 && (i+1) < raw_label.length) {
+ svg += `${letter}`;
+ }
+ }
+
+ for (let c of code) {
+
+ if (c === 's') {
+ xpos += small;
+ continue;
+ }
+ if (c === 'S') {
+ xpos += wide;
+ continue;
+ }
+
+ let w = c === 'B' ? wide : small;
+ let ypos = pagey + lto_label_height - height;
+
+ svg += ``;
+ xpos = xpos + w;
+ }
+ xpos += small;
+ }
+
+ return svg;
+}
+
+function html_page_header() {
+ let html = "";
+
+ html += "";
+
+ //html += "";
+ html += "";
+
+ return html;
+}
+
+function svg_page_header(page_width, page_height) {
+ let svg = "";
+
+ let html = html_page_header();
+ html += svg;
+ html += "";
+ html += "";
+
+ let frame = document.getElementById(target_id);
+
+ setupPrintFrame(frame, page_layout.page_width, page_layout.page_height);
+
+ let fwindow = frame.contentWindow;
+
+ fwindow.document.open();
+ fwindow.document.write(html);
+ fwindow.document.close();
+}
+
+function setupPrintFrame(frame, page_width, page_height) {
+ let dpi = 98;
+
+ let dpr = window.devicePixelRatio;
+ if (dpr !== undefined) {
+ dpi = dpi*dpr;
+ }
+
+ let ppmm = dpi/25.4;
+
+ frame.width = page_width*ppmm;
+ frame.height = page_height*ppmm;
+}
+
+function generate_calibration_page(target_id, page_layout, calibration) {
+
+ let frame = document.getElementById(target_id);
+
+ setupPrintFrame(frame, page_layout.page_width, page_layout.page_height);
+
+ let svg = svg_page_header( page_layout.page_width, page_layout.page_height);
+
+ svg += "";
+ svg += "";
+ svg += "";
+
+ svg += "";
+ svg += "";
+ svg += "";
+
+ svg += "";
+
+ let text_style = "style='font-weight:bold;font-size:4;font-family:sans-serif;'";
+
+ svg += `Sx = 50mm`;
+ svg += "";
+
+ svg += `Dx = 100mm`;
+ svg += "";
+
+ svg += `Sy = 50mm`;
+ svg += "";
+
+ svg += `Dy = 100mm`;
+ svg += "";
+
+ let c = calibration;
+ if (c !== undefined) {
+ svg += ``;
+ }
+
+ svg += "";
+
+ let html = html_page_header();
+ html += svg;
+ html += "