diff --git a/embroider_print.py b/embroider_print.py
index 96c3255d2..cbdaeb09d 100644
--- a/embroider_print.py
+++ b/embroider_print.py
@@ -9,7 +9,10 @@ import socket
import errno
import time
import logging
+from copy import deepcopy
import wx
+import appdirs
+import json
import inkex
import inkstitch
@@ -23,7 +26,7 @@ from jinja2 import Environment, FileSystemLoader, select_autoescape
from datetime import date
import base64
-from flask import Flask, request, Response, send_from_directory
+from flask import Flask, request, Response, send_from_directory, jsonify
import webbrowser
import requests
@@ -32,6 +35,29 @@ def datetimeformat(value, format='%Y/%m/%d'):
return value.strftime(format)
+def defaults_path():
+ defaults_dir = appdirs.user_config_dir('inkstitch')
+
+ if not os.path.exists(defaults_dir):
+ os.makedirs(defaults_dir)
+
+ return os.path.join(defaults_dir, 'print_settings.json')
+
+
+def load_defaults():
+ try:
+ with open(defaults_path(), 'r') as defaults_file:
+ defaults = json.load(defaults_file)
+ return defaults
+ except:
+ return {}
+
+
+def save_defaults(defaults):
+ with open(defaults_path(), 'w') as defaults_file:
+ json.dump(defaults, defaults_file)
+
+
def open_url(url):
# Avoid spurious output from xdg-open. Any output on stdout will crash
# inkscape.
@@ -71,6 +97,7 @@ def open_url(url):
class PrintPreviewServer(Thread):
def __init__(self, *args, **kwargs):
self.html = kwargs.pop('html')
+ self.metadata = kwargs.pop('metadata')
Thread.__init__(self, *args, **kwargs)
self.daemon = True
self.last_request_time = None
@@ -106,7 +133,7 @@ class PrintPreviewServer(Thread):
def shutdown():
self.shutting_down = True
request.environ.get('werkzeug.server.shutdown')()
- return 'Server shutting down...'
+ return _('Closing...') + '
' + _('It is safe to close this window now.')
@self.app.route('/resources/', methods=['GET'])
def resources(resource):
@@ -129,6 +156,27 @@ class PrintPreviewServer(Thread):
# nothing to do here -- request_started() will restart the watcher
return "OK"
+ @self.app.route('/settings/', methods=['POST'])
+ def set_field(field_name):
+ self.metadata[field_name] = request.json['value']
+ return "OK"
+
+ @self.app.route('/settings/', methods=['GET'])
+ def get_field(field_name):
+ return jsonify(self.metadata[field_name])
+
+ @self.app.route('/settings', methods=['GET'])
+ def get_settings():
+ settings = {}
+ settings.update(load_defaults())
+ settings.update(self.metadata)
+ return jsonify(settings)
+
+ @self.app.route('/defaults', methods=['POST'])
+ def set_defaults():
+ save_defaults(request.json['value'])
+ return "OK"
+
def stop(self):
# for whatever reason, shutting down only seems possible in
# the context of a flask request, so we'll just make one
@@ -295,11 +343,14 @@ class Print(InkstitchExtension):
'estimated_thread': '', # TODO
},
svg_overview = overview_svg,
- svg_scale = '100%',
color_blocks = stitch_plan.color_blocks,
)
- print_server = PrintPreviewServer(html=html)
+ # We've totally mucked with the SVG. Restore it so that we can save
+ # metadata into it.
+ self.document = deepcopy(self.original_document)
+
+ print_server = PrintPreviewServer(html=html, metadata=self.get_inkstitch_metadata())
print_server.start()
time.sleep(1)
@@ -310,12 +361,20 @@ class Print(InkstitchExtension):
info_frame.Show()
app.MainLoop()
- # don't let inkex print the document out
- sys.exit(0)
-
if __name__ == '__main__':
+ exception = None
+
save_stderr()
- effect = Print()
- effect.affect()
+ try:
+ effect = Print()
+ effect.affect()
+ except:
+ exception = traceback.format_exc()
restore_stderr()
+
+ if exception:
+ print >> sys.stderr, exception
+ sys.exit(1)
+ else:
+ sys.exit(0)
diff --git a/inkstitch/extensions.py b/inkstitch/extensions.py
index 7795abb8f..8d8a5fbb6 100644
--- a/inkstitch/extensions.py
+++ b/inkstitch/extensions.py
@@ -1,6 +1,95 @@
import inkex
+import re
+import json
+from copy import deepcopy
+from collections import MutableMapping
from .elements import AutoFill, Fill, Stroke, SatinColumn, Polyline, EmbroideryElement
from . import SVG_POLYLINE_TAG, SVG_GROUP_TAG, SVG_DEFS_TAG, INKSCAPE_GROUPMODE, EMBROIDERABLE_TAGS, PIXELS_PER_MM
+from .utils import cache
+
+
+SVG_METADATA_TAG = inkex.addNS("metadata", "svg")
+
+
+def strip_namespace(tag):
+ """Remove xml namespace from a tag name.
+
+ >>> {http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd}namedview
+ <<< namedview
+ """
+
+ match = re.match('^\{[^}]+\}(.+)$', tag)
+
+ if match:
+ return match.group(1)
+ else:
+ return tag
+
+
+class InkStitchMetadata(MutableMapping):
+ """Helper class to get and set inkstitch-specific metadata attributes.
+
+ Operates on a document and acts like a dict. Setting an item adds or
+ updates a metadata element in the document. Getting an item retrieves
+ a metadata element's text contents or None if an element by that name
+ doesn't exist.
+ """
+
+ def __init__(self, document):
+ self.document = document
+ self.metadata = self._get_or_create_metadata()
+
+ def _get_or_create_metadata(self):
+ metadata = self.document.find(SVG_METADATA_TAG)
+
+ if metadata is None:
+ metadata = inkex.etree.SubElement(self.document.getroot(), SVG_METADATA_TAG)
+
+ # move it so that it goes right after the first element, sodipodi:namedview
+ self.document.getroot().remove(metadata)
+ self.document.getroot().insert(1, metadata)
+
+ return metadata
+
+ # Because this class inherints from MutableMapping, all we have to do is
+ # implement these five methods and we get a full dict-like interface.
+
+ def __setitem__(self, name, value):
+ self._find_item(name).text = json.dumps(value)
+
+ def _find_item(self, name):
+ tag = inkex.addNS(name, "inkstitch")
+ item = self.metadata.find(tag)
+ if item is None:
+ item = inkex.etree.SubElement(self.metadata, tag)
+
+ return item
+
+ def __getitem__(self, name):
+ item = self._find_item(name)
+
+ try:
+ return json.loads(item.text)
+ except ValueError:
+ return None
+
+ def __delitem__(self, name):
+ item = self[name]
+
+ if item:
+ self.metadata.remove(item)
+
+ def __iter__(self):
+ for child in self.metadata:
+ if child.prefix == "inkstitch":
+ yield strip_namespace(child.tag)
+
+ def __len__(self):
+ i = 0
+ for i, item in enumerate(self):
+ pass
+
+ return i + 1
class InkstitchExtension(inkex.Effect):
@@ -101,3 +190,28 @@ class InkstitchExtension(inkex.Effect):
patches.extend(element.embroider(last_patch))
return patches
+
+ def get_inkstitch_metadata(self):
+ return InkStitchMetadata(self.document)
+
+ def parse(self):
+ """Override inkex.Effect to add Ink/Stitch xml namespace"""
+
+ # SVG parsers don't actually look for anything at this URL. They just
+ # care that it's unique. That defines a "namespace" of element and
+ # attribute names to disambiguate conflicts with element and
+ # attribute names other XML namespaces.
+ #
+ # Updating inkex.NSS here allows us to pass 'inkstitch' into
+ # inkex.addNS().
+ inkex.NSS['inkstitch'] = 'http://inkstitch.org/namespace'
+
+ # call the superclass's method first
+ inkex.Effect.parse(self)
+
+ # This is the only way I could find to add a namespace to an existing
+ # element tree at the top without getting ugly prefixes like "ns0".
+ inkex.etree.cleanup_namespaces(self.document,
+ top_nsmap=inkex.NSS,
+ keep_ns_prefixes=inkex.NSS.keys())
+ self.original_document = deepcopy(self.document)
diff --git a/messages.po b/messages.po
index aa2a05239..bce29caeb 100644
--- a/messages.po
+++ b/messages.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2018-04-02 22:11-0400\n"
+"POT-Creation-Date: 2018-04-28 13:36-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -98,6 +98,12 @@ msgid ""
"\"Overwrite\""
msgstr ""
+msgid "Closing..."
+msgstr ""
+
+msgid "It is safe to close this window now."
+msgstr ""
+
msgid ""
"A print preview has been opened in your web browser. This window will "
"stay open in order to communicate with the JavaScript code running in "
@@ -311,6 +317,9 @@ msgstr ""
msgid "Page"
msgstr ""
+msgid "Click to choose another logo"
+msgstr ""
+
msgid "Enter job title..."
msgstr ""
@@ -336,7 +345,7 @@ msgstr ""
msgid "Stops and Trims"
msgstr ""
-msgid "Estimated Time"
+msgid "Notes"
msgstr ""
msgid "Unique Colors"
@@ -360,12 +369,24 @@ msgstr ""
msgid "Total nr trims"
msgstr ""
+msgid "Enter operator notes..."
+msgstr ""
+
msgid "Job estimated time"
msgstr ""
+msgid "Ctrl + Scroll to Zoom"
+msgstr ""
+
msgid "Scale"
msgstr ""
+msgid "Fit"
+msgstr ""
+
+msgid "Apply to all"
+msgstr ""
+
msgid "COLOR"
msgstr ""
@@ -393,3 +414,12 @@ msgstr ""
msgid "Printing Size"
msgstr ""
+msgid "Print Layouts"
+msgstr ""
+
+msgid "Includes all settings visible here and also the icon."
+msgstr ""
+
+msgid "Save as defaults"
+msgstr ""
+
diff --git a/print/resources/inkstitch.js b/print/resources/inkstitch.js
index 454c9ae27..498b12112 100644
--- a/print/resources/inkstitch.js
+++ b/print/resources/inkstitch.js
@@ -1,3 +1,12 @@
+$.postJSON = function(url, data, success=null) {
+ return $.ajax(url, {
+ type: 'POST',
+ data: JSON.stringify(data),
+ contentType: 'application/json',
+ success: success
+ });
+};
+
function ping() {
$.get("/ping")
.done(function() { setTimeout(ping, 1000) })
@@ -13,46 +22,178 @@ function setPageNumbers() {
});
}
-// set preview svg scale to fit into its box
-function scaleInksimulation() {
- $('.inksimulation').each(function() {
+// Scale SVG (fit || full size)
+function scaleSVG(element, scale = 'fit') {
+
+ // always center svg
+ transform = "translate(-50%, -50%)";
+
+ if(scale == 'fit') {
var scale = Math.min(
- $(this).width() / $(this).find('svg').width(),
- $(this).height() / $(this).find('svg').height()
+ element.width() / element.find('svg').width(),
+ element.height() / element.find('svg').height()
);
+ }
+
+ transform += " scale(" + scale + ")";
+ var label = parseInt(scale*100);
- // center the SVG
- transform = "translate(-50%, -50%)";
+ element.find('svg').css({ transform: transform });
+ element.find('.scale').text(label);
+}
- if(scale <= 1) {
- transform += " scale(" + scale + ")";
- label = parseInt(scale*100) + '%';
- } else {
- label = "100%";
- }
+// set preview svg scale to fit into its box if transform is not set
+function scaleAllSvg() {
+ $('.page').each(function() {
+ if( $(this).find('.inksimulation svg').css('transform') == 'none') {
+ scaleSVG($(this).find('.inksimulation'), 'fit');
+ }
+ });
+}
- $(this).find('svg').css({ transform: transform });
- $(this).find('figcaption span').text(label);
- });
+var saveTimerHandles = {};
+
+function setSVGTransform(figure, transform) {
+ var field_name = $(figure).data('field-name');
+ var scale = transform.match(/-?[\d\.]+/g)[0];
+ figure.find('svg').css({ transform: transform });
+ figure.find(".scale").text(parseInt(scale*100));
+
+ // avoid spamming updates
+ if (saveTimerHandles[field_name] != null)
+ clearTimeout(saveTimerHandles[field_name]);
+
+ saveTimerHandles[field_name] = setTimeout(function() {
+ $.postJSON('/settings/' + field_name, {value: transform});
+ }, 250);
}
$(function() {
setTimeout(ping, 1000);
setPageNumbers();
- scaleInksimulation();
- /* Contendeditable Fields */
+ /* SCALING AND MOVING SVG */
- // When we focus out from a contenteditable field, we want to
- // set the same content to all fields with the same classname
- document.querySelectorAll('[contenteditable="true"]').forEach(function(elem) {
- elem.addEventListener('focusout', function() {
- var content = $(this).html();
- var field_name = $(this).attr('data-field-name');
- $('[data-field-name="' + field_name + '"]').html(content);
- });
+ /* Mousewheel scaling */
+ $('figure.inksimulation').on( 'DOMMouseScroll mousewheel', function (e) {
+ if(e.ctrlKey == true) {
+
+ var svg = $(this).find('svg');
+ var transform = svg.css('transform').match(/-?[\d\.]+/g);
+ var scale = parseFloat(transform[0]);
+
+ if (e.originalEvent.detail > 0 || e.originalEvent.wheelDelta < 0) {
+ // scroll down = zoom out
+ scale *= 0.97;
+ if (scale < 0.01)
+ scale = 0.01;
+ } else {
+ //scroll up
+ scale *= 1.03;
+ }
+
+ // set modified scale
+ transform[0] = scale;
+ transform[3] = scale;
+
+ setSVGTransform($(this), 'matrix(' + transform + ')');
+
+ //prevent page fom scrolling
+ return false;
+ }
});
+ /* Fit SVG */
+ $('button.svg-fit').click(function() {
+ var svgfigure = $(this).closest('figure');
+ scaleSVG(svgfigure, 'fit');
+ });
+
+ /* Full Size SVG */
+ $('button.svg-full').click(function() {
+ var svgfigure = $(this).closest('figure');
+ scaleSVG(svgfigure, '1');
+ });
+
+ /* Drag SVG */
+ $('figure.inksimulation').on('mousedown', function(e) {
+ var p0 = { x: e.pageX, y: e.pageY };
+ var start_transform = $(this).find('svg').css('transform').match(/-?[\d\.]+/g);
+ var start_offset = { x: parseFloat(start_transform[4]), y: parseFloat(start_transform[5]) };
+
+ $(this).css({cursor: 'move'});
+ $(this).on('mousemove', function(e) {
+ var p1 = { x: e.pageX, y: e.pageY };
+ // set modified translate
+ var transform = $(this).find('svg').css('transform').match(/-?[\d\.]+/g);
+ transform[4] = start_offset.x + (p1.x - p0.x);
+ transform[5] = start_offset.y + (p1.y - p0.y);
+
+ // I'd ike to use setSVGTransform() here but this code runs many
+ // times per second and it's just too CPU-intensive.
+ $(this).find('svg').css({transform: 'matrix(' + transform + ')'});
+ });
+ }).on('mouseup', function(e) {
+ $(this).css({cursor: 'auto'});
+ $(this).data('p0', null);
+ $(this).off('mousemove');
+
+ // set it using setSVGTransform() to ensure that it's saved to the server
+ setSVGTransform($(this), $(this).find('svg').css('transform'));
+ });
+
+ /* Apply transforms to All */
+ $('button.svg-apply').click(function() {
+ var transform = $(this).parent().siblings('svg').css('transform');
+ $('.inksimulation').each(function() {
+ setSVGTransform($(this), transform);
+ })
+ });
+
+ /* Contendeditable Fields */
+
+ $('[contenteditable="true"]').on('focusout', function() {
+ /* change svg scale */
+ var content = $(this).html();
+ var field_name = $(this).attr('data-field-name');
+ if(field_name == 'svg-scale') {
+ var scale = parseInt(content);
+ var svg = $(this).parent().siblings('svg');
+ var transform = svg.css('transform').match(/-?[\d\.]+/g);
+
+ transform[0] = scale / 100;
+ transform[3] = scale / 100;
+ svg.css({ transform: 'matrix(' + transform + ')' });
+ } else {
+ /* When we focus out from a contenteditable field, we want to
+ * set the same content to all fields with the same classname */
+ $('[data-field-name="' + field_name + '"]').html(content);
+ $.postJSON('/settings/' + field_name, {value: content});
+ }
+ });
+
+ // load up initial metadata values
+ $.getJSON('/settings', function(settings) {
+ $.each(settings, function(field_name, value) {
+ $('[data-field-name="' + field_name + '"]').each(function(i, item) {
+ var item = $(item);
+ if (item.is(':checkbox')) {
+ item.prop('checked', value).trigger('change');
+ } else if (item.is('img')) {
+ item.attr('src', value);
+ } else if (item.is('select')) {
+ item.val(value).trigger('change');
+ } else if (item.is('figure.inksimulation')) {
+ setSVGTransform(item, value);
+ } else {
+ item.text(value);
+ }
+ });
+ });
+ // wait until page size is set (if they've specified one) and then scale SVGs to fit
+ setTimeout(function() { scaleAllSvg() }, 500);
+ });
+
$('[contenteditable="true"]').keypress(function(e) {
if (e.which == 13) {
// pressing enter defocuses the element
@@ -64,14 +205,26 @@ $(function() {
return true;
}
});
-
-
+
+
/* Settings Bar */
-
+
$('button.close').click(function() {
$.post('/shutdown', {})
.done(function(data) {
window.close();
+
+ /* Chrome and Firefox both have a rule: scripts can only close windows
+ * that they opened. Chrome seems to have an exception for windows that
+ * were opened by an outside program, so the above works fine. Firefox
+ * steadfastly refuses to allow us to close the window, so we'll tell
+ * the user (in their language) that they can close it.
+ */
+ setTimeout(function() {
+ document.open();
+ document.write("" + data + "");
+ document.close();
+ }, 1000);
});
});
@@ -92,20 +245,54 @@ $(function() {
$('#close-settings').click(function(){
$('#settings-ui').hide();
});
-
+
/* Settings */
-
+
// Paper Size
$('select#printing-size').change(function(){
- $('.page').toggleClass('a4');
+ var size = $(this).find(':selected').val();
+ $('.page').toggleClass('a4', size == 'a4');
+ $.postJSON('/settings/paper-size', {value: size});
});
-
+
//Checkbox
$(':checkbox').change(function() {
- $('.' + this.id).toggle();
+ var checked = $(this).prop('checked');
+ var field_name = $(this).attr('data-field-name');
+
+ $('.' + field_name).toggle(checked);
setPageNumbers();
- scaleInksimulation();
+
+ $.postJSON('/settings/' + field_name, {value: checked});
+ });
+
+ // Logo
+ $('#logo-picker').change(function(e) {
+ var file = e.originalEvent.srcElement.files[0];
+ var reader = new FileReader();
+ reader.onloadend = function() {
+ var data = reader.result;
+ $('figure.brandlogo img').attr('src', data);
+ $.postJSON('/settings/logo', {value: data});
+ };
+ reader.readAsDataURL(file);
+ });
+
+ // "save as defaults" button
+ $('#save-settings').click(function(e) {
+ var settings = {};
+ settings["client-overview"] = $("[data-field-name='client-overview']").is(':checked');
+ settings["client-detailedview"] = $("[data-field-name='client-detailedview']").is(':checked');
+ settings["operator-overview"] = $("[data-field-name='operator-overview']").is(':checked');
+ settings["operator-detailedview"] = $("[data-field-name='operator-detailedview']").is(':checked');
+ settings["paper-size"] = $('select#printing-size').find(':selected').val();
+
+ var logo = $("figure.brandlogo img").attr('src');
+ if (logo.startsWith("data:")) {
+ settings["logo"] = logo;
+ }
+
+ $.postJSON('/defaults', {'value': settings});
});
-
});
diff --git a/print/resources/style.css b/print/resources/style.css
index 824f8dcef..97dee6a8e 100644
--- a/print/resources/style.css
+++ b/print/resources/style.css
@@ -70,6 +70,10 @@
margin: 0 !important;
}
+ figure.inksimulation div {
+ display: none;
+ }
+
.ui {
display: none;
}
@@ -77,7 +81,7 @@
#settings-ui {
display: none !important;
}
-
+
#errors {
display: none !important;
}
@@ -86,6 +90,10 @@
content: attr(data-label);
padding-right: 0.5em;
}
+
+ span.logo-instructions {
+ display: none;
+ }
}
@page {
@@ -109,7 +117,7 @@ body {
.page {
width: 210mm;
height: 275mm;
- padding: 5mm;
+ padding: 5mm;
background: #fff;
margin: 0 auto;
vertical-align: text-bottom;
@@ -163,13 +171,13 @@ body {
.ui button.close {
border: 1px solid rgb(197,5,5);
-
+
}
.ui button.close:hover {
background: rgb(197,5,5);
color: white;
-
+
}
.ui button.settings {
@@ -207,7 +215,7 @@ body {
border-bottom: 1px solid rgba(129, 129, 129, 0.5);
box-shadow: 0 1px 1px 1px rgba(194, 191, 191, 0.5);
}
-
+
#settings-ui div {
text-align: left;
font-size: 12pt;
@@ -226,6 +234,10 @@ body {
cursor: pointer;
}
+ #settings-ui fieldset {
+ margin-bottom: 1em;
+ }
+
/* Header */
@@ -247,18 +259,48 @@ body {
margin: 2.5mm;
}
+ figure.brandlogo label {
+ display: block;
+ width: 100%;
+ height: 100%;
+ line-height: 30mm;
+ text-align: center;
+ }
+
figure.brandlogo img {
max-width: 30mm;
max-height: 30mm;
+ display: inline;
+ vertical-align: middle;
}
-
+
+ /* hide the actual file picker control, since we just want them to click the
+ * image instead
+ */
+ #logo-picker {
+ width: 0px;
+ height: 0px;
+ opacity: 0%;
+ }
+
+ .logo-instructions {
+ white-space: nowrap;
+
+ /* chrome ignores this :(
+ text-align: center;
+ */
+
+ font-size: 10px;
+ color: rgb(192, 192, 192);
+ }
+
.operator-detailedview figure.brandlogo {
height: 20mm;
width: 30mm;
margin: 0 2.5mm;
text-align: left;
}
-
+
.operator-detailedview figure.brandlogo img {
max-width: 30mm;
max-height: 20mm;
@@ -314,15 +356,15 @@ body {
div.job-details p span:first-child {
font-weight: bold;
- padding-right: 1mm;
+ padding-right: 1mm;
}
div.job-details p span:last-child {
- text-align: left;
+ text-align: left;
}
div.job-details > div:last-child span {
- text-align: right !important;
+ text-align: right !important;
}
div.client-detailedview .job-details {
@@ -347,16 +389,16 @@ body {
font-size: 12pt;
font-weight: bold;
}
-
+
/* client dedailed view header */
.client-detailedview div.job-details p span:first-child {
- width: 20mm;
+ width: 20mm;
}
-
+
.client-detailedview div.job-details p span:last-child {
- max-width: 60mm;
+ max-width: 60mm;
}
-
+
/* SVG Preview Image */
@@ -370,7 +412,7 @@ body {
position: relative;
overflow: hidden;
}
-
+
.client-overview-main figure.inksimulation {
height: 155mm;
}
@@ -389,6 +431,24 @@ body {
font-weight: bold;
line-height: 12pt;
margin: 2.5mm;
+ background: rgba(255, 255, 255, 0.73);
+ padding: 5px;
+ }
+
+ figure.inksimulation div {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ margin-left: auto;
+ margin-right: auto;
+ width: fit-content;
+ }
+
+ figure.inksimulation button {
+ border: none;
+ background: grey;
+ color: white;
}
/* Color Swatches */
@@ -415,7 +475,7 @@ body {
font-weight: 700;
font-size: 12pt;
color: black;
- background: white;
+ background: white;
border: 0.5mm solid white;
margin: 0px;
padding: 0px;
@@ -446,44 +506,44 @@ body {
line-height: 30mm;
text-align: center;
}
-
+
/* detailedview color swatch */
-
+
.color-palette.detailed > div {
height: 100%;
position: relative;
}
-
+
.color-palette.detailed .color-info {
position: absolute;
top: 2mm;
left: 45mm;
}
-
+
.color-palette.detailed .color-info > div {
display: table;
}
-
+
.color-palette.detailed .color-info p {
display: table-row;
}
-
+
.color-palette.detailed .color-info span {
display: table-cell;
padding-right: 5mm;
}
-
+
/* Operator Detailed View */
.operator-detailedview header {
height: 25mm;
}
-
+
.operator-detailedveiw figure.brandlogo{
height: 15mm;
width: 15mm;
}
-
+
.operator-detailedveiw figure.brandlogo img {
max-width: 12.5mm;
max-height: 12.5mm;
@@ -497,21 +557,21 @@ body {
display: table;
width: 100%;
}
-
+
.operator-job-info div {
display: table-row;
}
-
+
div.job-headline {
display: table-header-group;
font-size: 9pt;
font-weight: bold;
}
-
+
div.job-headline p {
height: 1em;
}
-
+
.operator-job-info p {
height: 15mm;
max-width: 15mm;
@@ -521,11 +581,11 @@ body {
overflow: hidden;
border: 1px solid rgb(239,239,239);
}
-
+
.operator-job-info span {
display: block;
}
-
+
.operator-job-info span.color-index {
position: absolute;
top: 0;
@@ -533,17 +593,17 @@ body {
line-height: 15mm;
width: 10mm;
}
-
+
.operator-svg.operator-colorswatch {
width: 15mm;
}
-
+
.operator-svg.operator-preview {
min-width: 15mm;
max-width: 20mm;
height: 15mm;
}
-
+
.operator-svg svg {
position: absolute;
top: 0;
@@ -552,7 +612,7 @@ body {
max-width: 30mm;
height: 100%;
}
-
+
/* Footer */
@@ -565,7 +625,7 @@ body {
white-space: wrap;
text-align: center;
padding-top: 2mm;
-
+
}
footer p.num_pages {
@@ -625,7 +685,7 @@ body {
/* five items */
.color-swatch:first-child:nth-last-child(n+5),
.color-swatch:first-child:nth-last-child(n+5) ~ .color-swatch {
- font-size: 9pt;
+ font-size: 9pt;
width: calc(100% / 5);
}
@@ -665,7 +725,7 @@ body {
/* fourteen items */
.color-swatch:first-child:nth-last-child(n+14),
.color-swatch:first-child:nth-last-child(n+14) ~ .color-swatch {
- width: calc(100% / 5);
+ width: calc(100% / 5);
}
/* sixteen items */
@@ -717,7 +777,7 @@ body {
.color-swatch:first-child:nth-last-child(n+40) ~ .color-swatch {
width: calc(100% / 12);
}
-
+
/* fourty-nine items */
.color-swatch:first-child:nth-last-child(n+49),
.color-swatch:first-child:nth-last-child(n+40) ~ .color-swatch {
diff --git a/print/templates/headline.html b/print/templates/headline.html
index 649c02ea4..7a7059b98 100644
--- a/print/templates/headline.html
+++ b/print/templates/headline.html
@@ -1,12 +1,16 @@
-
{{ job.title }}
-
-
+
+
+
-
+
{{ date|datetimeformat(_('%Y.%m.%d')) }}
diff --git a/print/templates/index.html b/print/templates/index.html
index 21ca74ab4..0c9cedfdc 100644
--- a/print/templates/index.html
+++ b/print/templates/index.html
@@ -23,7 +23,7 @@
{% include 'operator_overview.html' %}
{# operator detailed view #}
- {% for color_block_part in color_blocks | batch(13) %}
+ {% for color_block_part in color_blocks | batch(12) %}
{% set outer_loop = loop %}
{% include 'operator_detailedview.html' %}
{% endfor %}
diff --git a/print/templates/operator_detailedview.html b/print/templates/operator_detailedview.html
index 633b9286d..f78028d7c 100644
--- a/print/templates/operator_detailedview.html
+++ b/print/templates/operator_detailedview.html
@@ -10,7 +10,7 @@
{{ _('Color') }}
{{ _('Thread Consumption') }}
{{ _('Stops and Trims') }}
- {{ _('Estimated Time') }}
+ {{ _('Notes') }}
{% if outer_loop.index == 1 %}
@@ -25,7 +25,7 @@
{{ _('Color Blocks') }}: {{ job.num_color_blocks }}
- {{ _('Design box size') }}: {{ job.dimensions }}
+ {{ _('Design box size') }}: {{ "%0.1fmm X %0.1fmm" | format(*job.dimensions) }}
{{ _('Total thread used') }}: {{job.estimated_thread }}
{{ _('Total stitch count') }}: {{job.num_stitches }}
@@ -34,7 +34,7 @@
{{ _('Total nr trims') }}: {{ job.num_trims }}
- {{ job.estimated_time }}
+
{% endif %}
@@ -45,7 +45,7 @@
@@ -65,7 +65,7 @@
{{ _('# trims') }}: {{ color_block.num_trims }}
- {{ color_block.estimatedtime }}
+
{% endfor %}
diff --git a/print/templates/operator_overview.html b/print/templates/operator_overview.html
index 25048ab76..367d6d2bf 100644
--- a/print/templates/operator_overview.html
+++ b/print/templates/operator_overview.html
@@ -11,7 +11,7 @@
-
{{ _('Design box size') }}:{{ job.dimensions }}
+
{{ _('Design box size') }}:{{ "%0.1fmm X %0.1fmm" | format(*job.dimensions) }}
{{ _('Total stitch count') }}:{{job.num_stitches }}
{{ _('Total thread used') }}:{{job.estimated_thread }}
@@ -25,9 +25,14 @@
-
{% include 'footer.html' %}
diff --git a/print/templates/print_detail.html b/print/templates/print_detail.html
index 18a70bbaf..714d33a24 100644
--- a/print/templates/print_detail.html
+++ b/print/templates/print_detail.html
@@ -15,9 +15,14 @@
-
+
{{color_block.svg_preview|safe}}
- {{ _('Scale') }} {{ svg_scale }}
+ {{ _('Scale') }} %
+
+
+
+
+
diff --git a/print/templates/print_overview.html b/print/templates/print_overview.html
index f5632cebc..efcf5b2eb 100644
--- a/print/templates/print_overview.html
+++ b/print/templates/print_overview.html
@@ -11,7 +11,7 @@
-
{{ _('Design box size') }}:{{ job.dimensions }}
+
{{ _('Design box size') }}:{{ "%0.1fmm X %0.1fmm" | format(*job.dimensions) }}
{{ _('Total stitch count') }}:{{job.num_stitches }}
{{ _('Total thread used') }}:{{job.estimated_thread }}
@@ -25,9 +25,14 @@
-
+
{{ svg_overview|safe }}
- {{ _('Scale') }} {{ svg_scale }}
+ {{ _('Scale') }} %
+
+
+
+
+
diff --git a/print/templates/ui.html b/print/templates/ui.html
index 078f1a4ca..f7246962a 100644
--- a/print/templates/ui.html
+++ b/print/templates/ui.html
@@ -15,7 +15,7 @@
{{ _('Settings') }}
{{ _('Printing Size') }}:
-