Commit ca76e740 authored by Michal Čihař's avatar Michal Čihař

Refactor widget rendering

Widgets are now classes which use inheritance to adjust their
parameters.

Fixes issue #190
parent 4a313d1e
......@@ -18,11 +18,9 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from django.conf import settings
from django.http import HttpResponse, Http404
from django.template import RequestContext
from django.shortcuts import render_to_response
from django.utils.translation import ugettext_lazy
from django.core.urlresolvers import reverse
from django.views.decorators.cache import cache_page
......@@ -30,107 +28,9 @@ from trans.util import get_site_url
from trans.models import Project
from lang.models import Language
from trans.forms import EnageLanguageForm
from trans.widgets import WIDGETS
from trans.views.helper import get_project, try_set_language
import cairo
import pango
import pangocairo
from cStringIO import StringIO
import os.path
WIDGETS = {
'287x66': {
'default': 'grey',
'colors': {
'grey': {
'bar': (0, 67.0 / 255, 118.0 / 255),
'border': (0, 0, 0),
'text': (0, 0, 0),
'line': 0.2,
},
'white': {
'bar': (0, 67.0 / 255, 118.0 / 255),
'border': (0, 0, 0),
'text': (0, 0, 0),
'line': 0.2,
},
'black': {
'bar': (0, 67.0 / 255, 118.0 / 255),
'border': (255, 255, 255),
'text': (255, 255, 255),
'line': 0.8,
},
},
'name': 'weblate-widget-%(color)s.png',
'progress': {
'x': 72,
'y': 52,
'height': 6,
'width': 180,
'horizontal': True,
},
'text': [
{
'text': "%(name)s",
'font': "Sans Bold",
'font_size': 10,
'pos': (72, 6),
},
{
# Translators: text in the engagement widget
'text': ugettext_lazy("translating %(count)d strings into %(languages)d languages\n%(percent)d%% complete, help us improve!"),
# Translators: text in the engagement widget, please use your language name instead of English
'text_lang': ugettext_lazy("translating %(count)d strings into English\n%(percent)d%% complete, help us improve!"),
'font': "Sans",
'font_size': 8,
'pos': (72, 22),
},
],
},
'88x31': {
'default': 'grey',
'colors': {
'grey': {
'bar': (0, 67.0 / 255, 118.0 / 255),
'border': (0, 0, 0),
'text': (0, 0, 0),
'line': 0.2,
},
'white': {
'bar': (0, 67.0 / 255, 118.0 / 255),
'border': (0, 0, 0),
'text': (0, 0, 0),
'line': 0.2,
},
'black': {
'bar': (0, 67.0 / 255, 118.0 / 255),
'border': (255, 255, 255),
'text': (255, 255, 255),
'line': 0.8,
},
},
'name': 'weblate-widget-%(widget)s-%(color)s.png',
'progress': None,
'text': [
{
'text': "%(name)s",
'font': "Sans Bold",
'font_size': 7,
'pos': (23, 2),
},
{
# Translators: text in the engagement widget
'text': ugettext_lazy('translation\n%(percent)d%% done'),
# Translators: text in the engagement widget, please use your language name instead of English
'text_lang': ugettext_lazy('English translation\n%(percent)d%% done'),
'font': "Sans",
'font_size': 7,
'pos': (23, 11),
},
],
}
}
def widgets_root(request):
return render_to_response('widgets-root.html', RequestContext(request, {
......@@ -162,9 +62,9 @@ def widgets(request, project):
)
widget_list = []
for widget_name in WIDGETS:
widget = WIDGETS[widget_name]
widget_class = WIDGETS[widget_name]
color_list = []
for color in widget['colors']:
for color in widget_class.colors:
if lang is None:
color_url = reverse(
'widget-image',
......@@ -204,21 +104,6 @@ def widgets(request, project):
}))
def render_text(pangocairo_context, line, text, params, font_size):
'''
Generates Pango layout for text.
'''
# Create pango layout and set font
layout = pangocairo_context.create_layout()
font = pango.FontDescription('%s %d' % (line['font'], font_size))
layout.set_font_description(font)
# Add text
layout.set_text(text)
return layout
@cache_page(3600)
def render(request, project, widget='287x66', color=None, lang=None):
obj = get_project(request, project)
......@@ -227,117 +112,19 @@ def render(request, project, widget='287x66', color=None, lang=None):
if lang is not None:
lang = try_set_language(lang)
percent = obj.get_translated_percent(lang)
# Get widget data
# Get widget class
try:
widget_data = WIDGETS[widget]
widget_class = WIDGETS[widget]
except KeyError:
raise Http404()
# Get widget color
if color not in widget_data['colors']:
color = widget_data['default']
# Background 287 x 66, logo 64 px
surface = cairo.ImageSurface.create_from_png(
os.path.join(settings.MEDIA_ROOT, widget_data['name'] % {
'color': color,
'widget': widget,
})
)
ctx = cairo.Context(surface)
# Setup
ctx.set_line_width(widget_data['colors'][color]['line'])
# Progress bar rendering
if widget_data['progress'] is not None:
# Filled bar
ctx.new_path()
ctx.set_source_rgb(*widget_data['colors'][color]['bar'])
if widget_data['progress']['horizontal']:
ctx.rectangle(
widget_data['progress']['x'],
widget_data['progress']['y'],
widget_data['progress']['width'] / 100.0 * percent,
widget_data['progress']['height']
)
else:
diff = widget_data['progress']['height'] / 100.0 * (100 - percent)
ctx.rectangle(
widget_data['progress']['x'],
widget_data['progress']['y'] + diff,
widget_data['progress']['width'],
widget_data['progress']['height'] - diff
)
ctx.fill()
# Progress border
ctx.new_path()
ctx.set_source_rgb(*widget_data['colors'][color]['border'])
ctx.rectangle(
widget_data['progress']['x'],
widget_data['progress']['y'],
widget_data['progress']['width'],
widget_data['progress']['height']
)
ctx.stroke()
# Text rendering
# Set text color
ctx.set_source_rgb(*widget_data['colors'][color]['text'])
# Create pango context
pangocairo_context = pangocairo.CairoContext(ctx)
pangocairo_context.set_antialias(cairo.ANTIALIAS_SUBPIXEL)
# Text format strings
params = {
'name': obj.name,
'count': obj.get_total(),
'languages': obj.get_language_count(),
'percent': percent,
}
for line in widget_data['text']:
# Format text
text = line['text']
if lang is not None and 'text_lang' in line:
text = line['text_lang']
if 'English' in text:
text = text.replace('English', lang.name)
text = text % params
font_size = line['font_size']
# Render text
layout = render_text(pangocairo_context, line, text, params, font_size)
# Fit text to image if it is too big
extent = layout.get_pixel_extents()
width = surface.get_width()
while extent[1][2] + line['pos'][0] > width and font_size > 4:
font_size -= 1
layout = render_text(
pangocairo_context,
line,
text,
params,
font_size
)
extent = layout.get_pixel_extents()
# Set position
ctx.move_to(*line['pos'])
# Construct object
widget = widget_class(obj, color, lang)
# Render to cairo context
pangocairo_context.update_layout(layout)
pangocairo_context.show_layout(layout)
# Render widget
widget.render()
# Render PNG
out = StringIO()
surface.write_to_png(out)
data = out.getvalue()
# Get image data
data = widget.get_image()
return HttpResponse(content_type='image/png', content=data)
# -*- coding: utf-8 -*-
#
# Copyright © 2012 - 2013 Michal Čihař <michal@cihar.com>
#
# This file is part of Weblate <http://weblate.org/>
#
# 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 3 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, see <http://www.gnu.org/licenses/>.
#
from django.conf import settings
from django.utils.translation import ugettext as _
import cairo
import pango
import pangocairo
from cStringIO import StringIO
import os.path
COLOR_DATA = {
'grey': {
'bar': (0, 67.0 / 255, 118.0 / 255),
'border': (0, 0, 0),
'text': (0, 0, 0),
},
'white': {
'bar': (0, 67.0 / 255, 118.0 / 255),
'border': (0, 0, 0),
'text': (0, 0, 0),
},
'black': {
'bar': (0, 67.0 / 255, 118.0 / 255),
'border': (255, 255, 255),
'text': (255, 255, 255),
},
}
WIDGETS = {}
def register_widget(widget):
'''
Registers widget in dictionary.
'''
WIDGETS[widget.name] = widget
class Widget(object):
'''
Generic widget class.
'''
name = None
colors = ('grey', 'white', 'black')
progress = None
def __init__(self, obj, color=None, lang=None):
'''
Creates Widget object.
'''
# Get object and related params
self.obj = obj
self.percent = obj.get_translated_percent(lang)
self.total = obj.get_total()
self.languages = obj.get_language_count()
self.params = self.get_text_params()
# Process parameters
self.color = self.get_color_name(color)
self.lang = lang
# Set rendering variables
self.surface = None
self.context = None
self.pango_context = None
self.width = 0
def get_color_name(self, color):
'''
Return color name based on allowed ones.
'''
if color not in self.colors:
return self.colors[0]
return color
def get_line_width(self):
'''
Returns line width for current widget.
'''
if self.color == 'black':
return 0.8
return 0.2
def get_text_params(self):
'''
Creates dictionary used for text formatting.
'''
return {
'name': self.obj.name,
'count': self.total,
'languages': self.languages,
'percent': self.percent,
}
def get_filename(self):
'''
Returns widgets filename.
'''
return os.path.join(
settings.MEDIA_ROOT,
'weblate-widget-%(widget)s-%(color)s.png' % {
'color': self.color,
'widget': self.name,
}
)
def render(self):
'''
Renders widget.
'''
# Surface with background image
self.surface = cairo.ImageSurface.create_from_png(
self.get_filename()
)
self.width = self.surface.get_width()
# Cairo context for graphics
self.context = cairo.Context(self.surface)
self.context.set_line_width(self.get_line_width())
# Pango context for rendering text
self.pango_context = pangocairo.CairoContext(self.context)
self.pango_context.set_antialias(cairo.ANTIALIAS_SUBPIXEL)
# Render progressbar
if self.progress:
self.render_progress()
# Render texts
self.context.set_source_rgb(*COLOR_DATA[self.color]['text'])
self.render_texts()
def render_progress(self):
'''
Renders progress bar.
'''
# Filled bar
self.context.new_path()
self.context.set_source_rgb(*COLOR_DATA[self.color]['bar'])
if self.progress['horizontal']:
self.context.rectangle(
self.progress['x'],
self.progress['y'],
self.progress['width'] / 100.0 * self.percent,
self.progress['height']
)
else:
diff = self.progress['height'] / 100.0 * (100 - self.percent)
self.context.rectangle(
self.progress['x'],
self.progress['y'] + diff,
self.progress['width'],
self.progress['height'] - diff
)
self.context.fill()
# Progress border
self.context.new_path()
self.context.set_source_rgb(*COLOR_DATA[self.color]['border'])
self.context.rectangle(
self.progress['x'],
self.progress['y'],
self.progress['width'],
self.progress['height']
)
self.context.stroke()
def get_text_layout(self, text, font_face, font_size):
'''
Generates Pango layout for text.
'''
# Create pango layout and set font
layout = self.pango_context.create_layout()
font = pango.FontDescription('%s %d' % (font_face, font_size))
layout.set_font_description(font)
# Add text
layout.set_text(text)
return layout
def render_text(self, text, lang_text, font_face, font_size, pos_x, pos_y):
# Use language variant if desired
if self.lang is not None and lang_text is not None:
text = lang_text
if 'English' in text:
text = text.replace('English', self.lang.name)
# Format text
text = text % self.params
# Iterate until text fits into widget
layout_width = self.width + pos_x + 1
layout = None
while layout_width + pos_x > self.width and font_size > 4:
layout = self.get_text_layout(text, font_face, font_size)
layout_width = layout.get_pixel_extents()[1][2]
font_size -= 1
# Set position
self.context.move_to(pos_x, pos_y)
# Render to cairo context
self.pango_context.update_layout(layout)
self.pango_context.show_layout(layout)
def render_texts(self):
'''
Text rendering method to be overridden.
'''
raise NotImplementedError()
def get_image(self):
'''
Returns PNG data.
'''
out = StringIO()
self.surface.write_to_png(out)
return out.getvalue()
class NormalWidget(Widget):
name = '287x66'
progress = {
'x': 72,
'y': 52,
'height': 6,
'width': 180,
'horizontal': True,
}
def render_texts(self):
self.render_text(
'%(name)s',
None,
'Sans Bold', 10,
72, 6
)
self.render_text(
_(
'translating %(count)d strings into %(languages)d languages\n'
'%(percent)d%% complete, help us improve!'
),
# Translators: please use your language name instead of English
_(
'translating %(count)d strings into English\n'
'%(percent)d%% complete, help us improve!'
),
'Sans', 8,
72, 22
)
register_widget(NormalWidget)
class SmallWidget(Widget):
name = '88x31'
def render_texts(self):
self.render_text(
'%(name)s',
None,
'Sans Bold', 7,
23, 2
)
self.render_text(
_('translation\n%(percent)d%% done'),
# Translators: please use your language name instead of English
_('English translation\n%(percent)d%% done'),
'Sans', 7,
23, 11
)
register_widget(SmallWidget)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment