diff --git a/apps/converter/api.py b/apps/converter/api.py index fb8b189745..f05c05f69b 100644 --- a/apps/converter/api.py +++ b/apps/converter/api.py @@ -9,6 +9,7 @@ from converter.conf.settings import OCR_OPTIONS from converter.conf.settings import DEFAULT_OPTIONS from converter.conf.settings import LOW_QUALITY_OPTIONS from converter.conf.settings import HIGH_QUALITY_OPTIONS +from converter.conf.settings import PRINT_QUALITY_OPTIONS from converter.conf.settings import GRAPHICS_BACKEND from converter.conf.settings import UNOCONV_PATH @@ -26,9 +27,14 @@ DEFAULT_OCR_FILE_FORMAT = u'tif' QUALITY_DEFAULT = u'quality_default' QUALITY_LOW = u'quality_low' QUALITY_HIGH = u'quality_high' +QUALITY_PRINT = u'quality_print' -QUALITY_SETTINGS = {QUALITY_DEFAULT: DEFAULT_OPTIONS, - QUALITY_LOW: LOW_QUALITY_OPTIONS, QUALITY_HIGH: HIGH_QUALITY_OPTIONS} +QUALITY_SETTINGS = { + QUALITY_DEFAULT: DEFAULT_OPTIONS, + QUALITY_LOW: LOW_QUALITY_OPTIONS, + QUALITY_HIGH: HIGH_QUALITY_OPTIONS, + QUALITY_PRINT: PRINT_QUALITY_OPTIONS +} CONVERTER_OFFICE_FILE_EXTENSIONS = [ u'ods', u'docx', u'doc' @@ -177,6 +183,15 @@ def get_page_count(input_filepath): return 1 +def get_document_dimensions(document, *args, **kwargs): + document_filepath = create_image_cache_filename(document.checksum, *args, **kwargs) + if os.path.exists(document_filepath): + options = [u'-format', u'%w %h'] + return [int(dimension) for dimension in backend.execute_identify(unicode(document_filepath), options).split()] + else: + return [0, 0] + + def convert_document_for_ocr(document, page=DEFAULT_PAGE_INDEX_NUMBER, file_format=DEFAULT_OCR_FILE_FORMAT): #Extract document file input_filepath = document_save_to_temp_dir(document, document.uuid) diff --git a/apps/converter/backends/graphicsmagick.py b/apps/converter/backends/graphicsmagick.py index 52f298033b..a1a0bc5797 100644 --- a/apps/converter/backends/graphicsmagick.py +++ b/apps/converter/backends/graphicsmagick.py @@ -9,11 +9,12 @@ CONVERTER_ERROR_STRING_NO_DECODER = u'No decode delegate for this image format' CONVERTER_ERROR_STARTS_WITH = u'starts with' -def execute_identify(input_filepath, arguments=u''): +def execute_identify(input_filepath, arguments=None): command = [] command.append(unicode(GM_PATH)) command.append(u'identify') - command.extend(unicode(arguments).split()) + if arguments: + command.extend(arguments) command.append(unicode(input_filepath)) proc = subprocess.Popen(command, close_fds=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE) return_code = proc.wait() diff --git a/apps/converter/backends/imagemagick.py b/apps/converter/backends/imagemagick.py index c60f6bc341..a2b99b2c96 100644 --- a/apps/converter/backends/imagemagick.py +++ b/apps/converter/backends/imagemagick.py @@ -9,10 +9,11 @@ from converter.exceptions import ConvertError, UnknownFormat, \ CONVERTER_ERROR_STRING_NO_DECODER = u'no decode delegate for this image format' -def execute_identify(input_filepath, arguments=u''): +def execute_identify(input_filepath, arguments=None): command = [] command.append(unicode(IM_IDENTIFY_PATH)) - command.extend(unicode(arguments).split()) + if arguments: + command.extend(arguments) command.append(unicode(input_filepath)) proc = subprocess.Popen(command, close_fds=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE) diff --git a/apps/converter/conf/settings.py b/apps/converter/conf/settings.py index 2a4e417431..7b23e72243 100644 --- a/apps/converter/conf/settings.py +++ b/apps/converter/conf/settings.py @@ -14,6 +14,7 @@ OCR_OPTIONS = getattr(settings, 'CONVERTER_OCR_OPTIONS', u'-colorspace Gray -dep DEFAULT_OPTIONS = getattr(settings, 'CONVERTER_DEFAULT_OPTIONS', u'') LOW_QUALITY_OPTIONS = getattr(settings, 'CONVERTER_LOW_QUALITY_OPTIONS', u'') HIGH_QUALITY_OPTIONS = getattr(settings, 'CONVERTER_HIGH_QUALITY_OPTIONS', u'-density 400') +PRINT_QUALITY_OPTIONS = getattr(settings, 'CONVERTER_HIGH_QUALITY_OPTIONS', u'-density 500') setting_description = { diff --git a/apps/documents/__init__.py b/apps/documents/__init__.py index 5f7a287198..d0b4c5db09 100644 --- a/apps/documents/__init__.py +++ b/apps/documents/__init__.py @@ -52,6 +52,7 @@ document_find_duplicates = {'text': _(u'find duplicates'), 'view': 'document_fin document_find_all_duplicates = {'text': _(u'find all duplicates'), 'view': 'document_find_all_duplicates', 'famfam': 'page_refresh', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_VIEW]}, 'description': _(u'Search all the documents\' checksums and return a list of the exact matches.')} document_clear_transformations = {'text': _(u'clear all transformations'), 'view': 'document_clear_transformations', 'args': 'object.id', 'famfam': 'page_paintbrush', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_TRANSFORM]}} document_multiple_clear_transformations = {'text': _(u'clear all transformations'), 'view': 'document_multiple_clear_transformations', 'famfam': 'page_paintbrush', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_TRANSFORM]}} +document_print = {'text': _(u'print'), 'view': 'document_print', 'args': 'object.id', 'famfam': 'printer', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_VIEW]}} document_page_transformation_list = {'text': _(u'page transformations'), 'class': 'no-parent-history', 'view': 'document_page_transformation_list', 'args': 'object.id', 'famfam': 'pencil_go', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_TRANSFORM]}} document_page_transformation_create = {'text': _(u'create new transformation'), 'class': 'no-parent-history', 'view': 'document_page_transformation_create', 'args': 'object.id', 'famfam': 'pencil_add', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_TRANSFORM]}} @@ -82,7 +83,7 @@ metadata_group_create_sibling = {'text': _(u'upload new document using same meta staging_file_preview = {'text': _(u'preview'), 'class': 'fancybox-noscaling', 'view': 'staging_file_preview', 'args': 'object.id', 'famfam': 'drive_magnify'} staging_file_delete = {'text': _(u'delete'), 'view': 'staging_file_delete', 'args': 'object.id', 'famfam': 'drive_delete'} -register_links(Document, [document_view_simple, document_view, document_edit, document_edit_metadata, document_delete, document_download, document_find_duplicates, document_clear_transformations]) +register_links(Document, [document_view_simple, document_view, document_edit, document_edit_metadata, document_print, document_delete, document_download, document_find_duplicates, document_clear_transformations]) register_links(Document, [document_create_sibling], menu_name='sidebar') register_multi_item_links(['metadatagroup_view', 'document_list', 'document_list_recent'], [document_multiple_clear_transformations, document_multiple_edit_metadata, document_multiple_delete]) diff --git a/apps/documents/conf/settings.py b/apps/documents/conf/settings.py index aa518b5dd5..6ea4c16070 100644 --- a/apps/documents/conf/settings.py +++ b/apps/documents/conf/settings.py @@ -59,6 +59,7 @@ STORAGE_BACKEND = getattr(settings, 'DOCUMENTS_STORAGE_BACKEND', FileBasedStorag # Usage PREVIEW_SIZE = getattr(settings, 'DOCUMENTS_PREVIEW_SIZE', '640x480') +PRINT_SIZE = getattr(settings, 'DOCUMENTS_PREVIEW_SIZE', '1400') MULTIPAGE_PREVIEW_SIZE = getattr(settings, 'DOCUMENTS_MULTIPAGE_PREVIEW_SIZE', '160x120') THUMBNAIL_SIZE = getattr(settings, 'DOCUMENTS_THUMBNAIL_SIZE', '50x50') DISPLAY_SIZE = getattr(settings, 'DOCUMENTS_DISPLAY_SIZE', '1200') diff --git a/apps/documents/templates/document_print.html b/apps/documents/templates/document_print.html new file mode 100644 index 0000000000..bd5cc3d648 --- /dev/null +++ b/apps/documents/templates/document_print.html @@ -0,0 +1,54 @@ +{% load project_tags %} +{% load printing_tags %} + + + + + + {% project_name %} + + + + + + + {% get_document_size object %} + {{ document_width }} {{ document_height }} + document_height %}width="100%"{% else %}height="100%"{% endif %} /> + + diff --git a/apps/documents/templatetags/__init__.py b/apps/documents/templatetags/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/documents/templatetags/printing_tags.py b/apps/documents/templatetags/printing_tags.py new file mode 100644 index 0000000000..aa59a40c3a --- /dev/null +++ b/apps/documents/templatetags/printing_tags.py @@ -0,0 +1,24 @@ +from django.template import TemplateSyntaxError, Library, \ + VariableDoesNotExist, Node, Variable +from converter.api import get_document_dimensions, QUALITY_PRINT +from documents.views import calculate_converter_arguments +from documents.conf.settings import PRINT_SIZE + +register = Library() + + +class GetImageSizeNode(Node): + def __init__(self, document): + self.document = document + + def render(self, context): + document = Variable(self.document).resolve(context) + arguments, warnings = calculate_converter_arguments(document, size=PRINT_SIZE, quality=QUALITY_PRINT) + context[u'document_width'], context['document_height'] = get_document_dimensions(document, **arguments) + return u'' + +@register.tag +def get_document_size(parser, token): + tag_name, arg = token.contents.split(None, 1) + + return GetImageSizeNode(document=arg) diff --git a/apps/documents/urls.py b/apps/documents/urls.py index dbf8e1dd4d..c85e714815 100644 --- a/apps/documents/urls.py +++ b/apps/documents/urls.py @@ -1,12 +1,13 @@ from django.conf.urls.defaults import patterns, url from documents.conf.settings import PREVIEW_SIZE +from documents.conf.settings import PRINT_SIZE from documents.conf.settings import THUMBNAIL_SIZE from documents.conf.settings import DISPLAY_SIZE from documents.conf.settings import MULTIPAGE_PREVIEW_SIZE from documents.conf.settings import ENABLE_SINGLE_DOCUMENT_UPLOAD -from converter.api import QUALITY_HIGH +from converter.api import QUALITY_HIGH, QUALITY_PRINT urlpatterns = patterns('documents.views', url(r'^document/list/$', 'document_list', (), 'document_list'), @@ -22,11 +23,13 @@ urlpatterns = patterns('documents.views', url(r'^document/(?P\d+)/edit/$', 'document_edit', (), 'document_edit'), url(r'^document/(?P\d+)/edit/metadata/$', 'document_edit_metadata', (), 'document_edit_metadata'), url(r'^document/multiple/edit/metadata/$', 'document_multiple_edit_metadata', (), 'document_multiple_edit_metadata'), + url(r'^document/(?P\d+)/print/$', 'document_print', (), 'document_print'), url(r'^document/(?P\d+)/display/preview/$', 'get_document_image', {'size': PREVIEW_SIZE}, 'document_preview'), url(r'^document/(?P\d+)/display/preview/multipage/$', 'get_document_image', {'size': MULTIPAGE_PREVIEW_SIZE}, 'document_preview_multipage'), url(r'^document/(?P\d+)/display/thumbnail/$', 'get_document_image', {'size': THUMBNAIL_SIZE}, 'document_thumbnail'), url(r'^document/(?P\d+)/display/$', 'get_document_image', {'size': DISPLAY_SIZE, 'quality': QUALITY_HIGH}, 'document_display'), + url(r'^document/(?P\d+)/display/print/$', 'get_document_image', {'size': PRINT_SIZE, 'quality': QUALITY_PRINT}, 'document_display_print'), url(r'^document/(?P\d+)/download/$', 'document_download', (), 'document_download'), url(r'^document/(?P\d+)/create/siblings/$', 'document_create_sibling', {'multiple': True if ENABLE_SINGLE_DOCUMENT_UPLOAD == False else False}, 'document_create_sibling'), diff --git a/apps/documents/views.py b/apps/documents/views.py index 4d053d3f28..a8a9fac285 100644 --- a/apps/documents/views.py +++ b/apps/documents/views.py @@ -27,6 +27,8 @@ from permissions.api import check_permissions from navigation.utils import resolve_to_name from tags.utils import get_tags_subtemplate from document_comments.utils import get_comments_subtemplate +from converter.api import DEFAULT_ZOOM_LEVEL, DEFAULT_ROTATION, \ + DEFAULT_FILE_FORMAT from documents.conf.settings import DELETE_STAGING_FILE_AFTER_UPLOAD from documents.conf.settings import USE_STAGING_DIRECTORY @@ -537,6 +539,30 @@ def document_multiple_edit_metadata(request): return document_edit_metadata(request, document_id_list=request.GET.get('id_list', [])) +def calculate_converter_arguments(document, *args, **kwargs): + size = kwargs.pop('size', PREVIEW_SIZE) + quality = kwargs.pop('quality', QUALITY_DEFAULT) + page = kwargs.pop('page', 1) + file_format = kwargs.pop('file_format', DEFAULT_FILE_FORMAT) + zoom = kwargs.pop('zoom', DEFAULT_ZOOM_LEVEL) + rotation = kwargs.pop('rotation', DEFAULT_ROTATION) + + document_page = DocumentPage.objects.get(document=document, page_number=page) + transformation_string, warnings = document_page.get_transformation_string() + + arguments = { + 'size': size, + 'file_format': file_format, + 'quality': quality, + 'extra_options': transformation_string, + 'page': page - 1, + 'zoom': zoom, + 'rotation': rotation + } + + return arguments, warnings + + def get_document_image(request, document_id, size=PREVIEW_SIZE, quality=QUALITY_DEFAULT): check_permissions(request.user, 'documents', [PERMISSION_DOCUMENT_VIEW]) @@ -544,24 +570,24 @@ def get_document_image(request, document_id, size=PREVIEW_SIZE, quality=QUALITY_ page = int(request.GET.get('page', 1)) + zoom = int(request.GET.get('zoom', 100)) + + if zoom < ZOOM_MIN_LEVEL: + zoom = ZOOM_MIN_LEVEL + + if zoom > ZOOM_MAX_LEVEL: + zoom = ZOOM_MAX_LEVEL + + rotation = int(request.GET.get('rotation', 0)) % 360 + + arguments, warnings = calculate_converter_arguments(document, size=size, file_format=DEFAULT_FILE_FORMAT, quality=quality, page=page, zoom=zoom, rotation=rotation) + + if warnings and (request.user.is_staff or request.user.is_superuser): + for warning in warnings: + messages.warning(request, _(u'Page transformation error: %s') % warning) + try: - document_page = DocumentPage.objects.get(document=document, page_number=page) - transformation_string, warnings = document_page.get_transformation_string() - if warnings and (request.user.is_staff or request.user.is_superuser): - for warning in warnings: - messages.warning(request, _(u'Page transformation error: %s') % warning) - - zoom = int(request.GET.get('zoom', 100)) - - if zoom < ZOOM_MIN_LEVEL: - zoom = ZOOM_MIN_LEVEL - - if zoom > ZOOM_MAX_LEVEL: - zoom = ZOOM_MAX_LEVEL - - rotation = int(request.GET.get('rotation', 0)) % 360 - - output_file = convert_document(document, size=size, file_format=u'jpg', quality=quality, extra_options=transformation_string, page=page - 1, zoom=zoom, rotation=rotation) + output_file = convert_document(document, **arguments) except UnkownConvertError, e: if request.user.is_staff or request.user.is_superuser: messages.error(request, e) @@ -1126,3 +1152,15 @@ def metadatagroup_view(request, document_id, metadata_group_id): 'hide_links': True, 'ref_object': document }, context_instance=RequestContext(request)) + + +def document_print(request, document_id): + check_permissions(request.user, 'documents', [PERMISSION_DOCUMENT_VIEW]) + + document = get_object_or_404(Document.objects.select_related(), pk=document_id) + + RecentDocument.objects.add_document_for_user(request.user, document) + + return render_to_response('document_print.html', { + 'object': document, + }, context_instance=RequestContext(request)) diff --git a/site_media/packages/jquery.printElement.min.js b/site_media/packages/jquery.printElement.min.js new file mode 100644 index 0000000000..d1aba537e3 --- /dev/null +++ b/site_media/packages/jquery.printElement.min.js @@ -0,0 +1,28 @@ +/// +/* +* Print Element Plugin 1.2 +* +* Copyright (c) 2010 Erik Zaadi +* +* Inspired by PrintArea (http://plugins.jquery.com/project/PrintArea) and +* http://stackoverflow.com/questions/472951/how-do-i-print-an-iframe-from-javascript-in-safari-chrome +* +* Home Page : http://projects.erikzaadi/jQueryPlugins/jQuery.printElement +* Issues (bug reporting) : http://github.com/erikzaadi/jQueryPlugins/issues/labels/printElement +* jQuery plugin page : http://plugins.jquery.com/project/printElement +* +* Thanks to David B (http://github.com/ungenio) and icgJohn (http://www.blogger.com/profile/11881116857076484100) +* For their great contributions! +* +* Dual licensed under the MIT and GPL licenses: +* http://www.opensource.org/licenses/mit-license.php +* http://www.gnu.org/licenses/gpl.html +* +* Note, Iframe Printing is not supported in Opera and Chrome 3.0, a popup window will be shown instead +*/ +;(function(g){function k(c){c&&c.printPage?c.printPage():setTimeout(function(){k(c)},50)}function l(c){c=a(c);a(":checked",c).each(function(){this.setAttribute("checked","checked")});a("input[type='text']",c).each(function(){this.setAttribute("value",a(this).val())});a("select",c).each(function(){var b=a(this);a("option",b).each(function(){b.val()==a(this).val()&&this.setAttribute("selected","selected")})});a("textarea",c).each(function(){var b=a(this).attr("value");if(a.browser.b&&this.firstChild)this.firstChild.textContent= +b;else this.innerHTML=b});return a("
").append(c.clone()).html()}function m(c,b){var i=a(c);c=l(c);var d=[];d.push(""+b.pageTitle+"");if(b.overrideElementCSS){if(b.overrideElementCSS.length>0)for(var f=0;f'):d.push('')}}else a("link",j).filter(function(){return a(this).attr("rel").toLowerCase()== +"stylesheet"}).each(function(){d.push('')});d.push('');d.push('');d.push('
'+c+"
");d.push('