ImageField.py 9.3 KB
Newer Older
1
# -*- coding: utf-8 -*-
Jean-Paul Smets's avatar
Jean-Paul Smets committed
2 3 4
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
5
#                    Jean-Paul Smets-Solanes <jp@nexedi.com>
Jean-Paul Smets's avatar
Jean-Paul Smets committed
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################

from Products.Formulator import Widget, Validator
from Products.Formulator.Field import ZMIField
from Products.Formulator.DummyField import fields
33
from OFS.Image import Image as OFSImage
34 35
from lxml.etree import Element
from lxml import etree
36
import re
37

38 39
DRAW_URI = 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0'
TEXT_URI = 'urn:oasis:names:tc:opendocument:xmlns:text:1.0'
40 41 42
XLINK_URI = 'http://www.w3.org/1999/xlink'
SVG_URI = 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0'

43 44 45 46

NSMAP = {
          'draw': DRAW_URI,
          'text': TEXT_URI,
47 48
          'xlink': XLINK_URI,
          'svg': SVG_URI
49
        }
Jean-Paul Smets's avatar
Jean-Paul Smets committed
50 51

class ImageFieldWidget(Widget.TextWidget):
52
    """ImageField widget.
Fabien Morin's avatar
Fabien Morin committed
53

54 55 56
    Renders an HTML <img> element where the src is the 'default' field value.
    The 'description' field value is used as 'alt' attribute.
    The image size is calculated using 'image_display'.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
57 58 59 60 61 62 63
    """
    property_names = Widget.TextWidget.property_names + \
      ['image_display', 'image_format','image_resolution']

    image_display = fields.StringField('image_display',
                               title='Image Display',
                               description=(
64 65
        "The display size. See ERP5.Document.Image.default_displays_id_list "
        "for possible values. This is only used with ERP5 Images."),
Jean-Paul Smets's avatar
Jean-Paul Smets committed
66
                               default='thumbnail',
Kazuhiko Shiozaki's avatar
Kazuhiko Shiozaki committed
67
                               required=0)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
68 69 70 71

    image_format = fields.StringField('image_format',
                               title='Image Format',
                               description=(
72 73
        "The format in which the image should be converted to. "
        "This is only used with ERP5 Images."),
Jean-Paul Smets's avatar
Jean-Paul Smets committed
74 75 76 77 78 79
                               default='',
                               required=0)

    image_resolution = fields.IntegerField('image_resolution',
                               title='Image Resolution',
                               description=(
80 81
        "The resolution used when converting the image. "
        "This is only used with ERP5 Images."),
Jean-Paul Smets's avatar
Jean-Paul Smets committed
82 83 84
                               default=75,
                               required=0)

85
    def render(self, field, key, value, REQUEST, render_prefix=None):
86 87
        """Render image field as a link to the image
        """
88
        return self.render_view(field, value, REQUEST=REQUEST)
89

90
    def render_view(self, field, value, REQUEST=None, render_prefix=None):
91
        """Render image field as a link to the image
Jean-Paul Smets's avatar
Jean-Paul Smets committed
92
        """
93
        # Url is already defined in value
94 95
        if value is None:
          return ''
96
        image = value
97 98
        alt = field.get_value('description') or \
              field.get_value('title')
99 100
        css_class = field.get_value('css_class')
        extra = field.get_value('extra')
Kazuhiko Shiozaki's avatar
Kazuhiko Shiozaki committed
101 102 103 104 105 106 107 108
        options = {}
        options['display'] = field.get_value('image_display')
        options['format'] = field.get_value('image_format')
        options['resolution'] = field.get_value('image_resolution')
        parameters = '&'.join(['%s=%s' % (k, v) for k, v in options.items() \
                               if v])
        if parameters:
            image = '%s?%s' % (image, parameters)
109 110 111
        return Widget.render_element(
            "img",
            alt=alt,
Kazuhiko Shiozaki's avatar
Kazuhiko Shiozaki committed
112
            src=image,
113 114 115
            css_class=css_class,
            extra=extra,
        )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
116

Nicolas Delaby's avatar
Nicolas Delaby committed
117 118
    def render_odg_view(self, field, value, as_string, ooo_builder, REQUEST,
                        render_prefix, attr_dict, local_name):
119 120 121 122 123 124 125 126 127
      """
        return an image xml node rendered in odg format
        if as_string is True (default) the returned value is a string (xml
        reprensation of the node), if it's False, the value returned is the node
        object.
        attr_dict can be used for additional parameters (like style).
      """
      if attr_dict is None:
        attr_dict = {}
128 129 130 131 132 133 134 135 136 137 138 139 140 141
      draw_frame_node = None
      if value in ('', None):
        return None
      path = '/'.join(REQUEST.physicalPathFromURL(value))
      path = path.encode()
      image_object = field.getPortalObject().restrictedTraverse(path)
      display = field.get_value('image_display')
      format = field.get_value('image_format')
      image_parameter_dict = { 'display': display,
                               'format':format}
      # convert the image using fields parameters. In this way, if an image
      # is displayed in the form as a thumbnail, it will be added in the odg
      # document as thumbnail size/quality
      content_type, image_data = image_object.convert(**image_parameter_dict)
142 143 144 145 146 147
      if isinstance(image_data, str):
        image = OFSImage('', '', image_data)
      else:
        image = image_data
      width = image.width
      height = image.height
148 149
      if image_data is None:
        return draw_frame_node
150

151 152 153 154
      # Big images are cut into smaller chunks, so it's required to cast to
      # str. See OFS/Image -> _read_data method for more informations
      image_data = str(image_data)

155 156 157 158 159
      format = content_type.split('/')[-1]
      # add the image to the odg document
      picture_path = ooo_builder.addImage(image=image_data, format=format)

      # create the xml nodes related to the image
160 161
      draw_frame_tag_name = '{%s}%s' % (DRAW_URI, 'frame')
      draw_frame_node = Element(draw_frame_tag_name, nsmap=NSMAP)
162 163 164 165 166
      draw_frame_node.attrib.update(attr_dict.get(draw_frame_tag_name,
        {}).pop(0))

      # set the size of the image
      if display is not None:
Nicolas Delaby's avatar
Nicolas Delaby committed
167 168 169 170 171
        # if the image width and height are not on defined, use current
        # width and height
        if (image_object.getWidth(), image_object.getHeight()) not in \
          ((-1, -1), (0,0)):
          width, height = image_object._getAspectRatioSize(width, height)
172 173
          if draw_frame_node.attrib.get('{%s}width' % SVG_URI) and \
            draw_frame_node.attrib.get('{%s}height' % SVG_URI):
Nicolas Delaby's avatar
Nicolas Delaby committed
174 175 176 177
            # if a size already exist from attr_dict, try to resize the image to
            # fit this size (image should not be biger than size from attr_dict)
            # devide the value by 20 to have cm instead of px
            width, height = self._getPictureSize(width/20., height/20.,
178 179
                target_width=draw_frame_node.attrib.get('{%s}width' % SVG_URI, ''),
                target_height=draw_frame_node.attrib.get('{%s}height' % SVG_URI, ''))
Nicolas Delaby's avatar
Nicolas Delaby committed
180 181 182

          draw_frame_node.set('{%s}width' % SVG_URI, str(width))
          draw_frame_node.set('{%s}height' % SVG_URI, str(height))
183 184 185

      image_tag_name = '{%s}%s' % (DRAW_URI, 'image')
      image_node = Element(image_tag_name, nsmap=NSMAP)
186
      image_node.attrib.update(attr_dict.get(image_tag_name, []).pop())
187 188 189
      image_node.set('{%s}href' % XLINK_URI, picture_path)

      draw_frame_node.append(image_node)
190 191 192 193
      if as_string:
        return etree.tostring(draw_frame_node)
      return draw_frame_node

194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
    def _getPictureSize(self, picture_width, picture_height, target_width,
        target_height):
      # if not match causes exception
      width_tuple = re.match("(\d[\d\.]*)(.*)", target_width).groups()
      height_tuple = re.match("(\d[\d\.]*)(.*)", target_height).groups()
      unit = width_tuple[1]
      w = float(width_tuple[0])
      h = float(height_tuple[0])
      aspect_ratio = 1
      try: # try image properties
        aspect_ratio = picture_width / picture_height
      except (TypeError, ZeroDivisionError):
        try: # try ERP5.Document.Image API
          height = picture_height
          if height:
            aspect_ratio = picture_width / height
        except AttributeError: # fallback to Photo API
          height = float(picture_height)
          if height:
            aspect_ratio = picture_width / height
      resize_w = h * aspect_ratio
      resize_h = w / aspect_ratio
      if resize_w < w:
        w = resize_w
      elif resize_h < h:
        h = resize_h
      return (str(w) + unit, str(h) + unit)

Jean-Paul Smets's avatar
Jean-Paul Smets committed
222 223 224 225 226 227 228 229 230 231
ImageFieldWidgetInstance = ImageFieldWidget()
ImageFieldValidatorInstance = Validator.StringValidator()

class ImageField(ZMIField):
    meta_type = "ImageField"

    widget = ImageFieldWidgetInstance
    validator = ImageFieldValidatorInstance