kopia lustrzana https://github.com/hholzgra/ocitysmap
285 wiersze
11 KiB
Python
285 wiersze
11 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# ocitysmap, city map and street index generator from OpenStreetMap data
|
|
# Copyright (C) 2010 David Decotigny
|
|
# Copyright (C) 2010 Frédéric Lehobey
|
|
# Copyright (C) 2010 Pierre Mauduit
|
|
# Copyright (C) 2010 David Mentré
|
|
# Copyright (C) 2010 Maxime Petazzoni
|
|
# Copyright (C) 2010 Thomas Petazzoni
|
|
# Copyright (C) 2010 Gaël Utard
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Affero General Public License as
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
# License, or 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 Affero General Public License for more details.
|
|
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import os
|
|
import pango
|
|
import sys
|
|
|
|
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
|
import draw_utils
|
|
|
|
|
|
class IndexEmptyError(Exception):
|
|
"""This exception is raised when no data is to be rendered in the index."""
|
|
pass
|
|
|
|
class IndexDoesNotFitError(Exception):
|
|
"""This exception is raised when the index does not fit in the given
|
|
graphical area, even after trying smaller font sizes."""
|
|
pass
|
|
|
|
NUMBER_CATEGORY_NAME = '0-9'
|
|
|
|
class IndexCategory:
|
|
"""
|
|
The IndexCategory represents a set of index items that belong to the same
|
|
category (their first letter is the same or they are of the same amenity
|
|
type).
|
|
"""
|
|
name = None
|
|
items = None
|
|
is_street = False
|
|
|
|
def __init__(self, name, items=None, is_street=True):
|
|
assert name is not None
|
|
self.name = name
|
|
self.items = items or list()
|
|
self.is_street = is_street
|
|
|
|
def __str__(self):
|
|
return '<%s (%s)>' % (self.name, map(str, self.items))
|
|
|
|
def __repr__(self):
|
|
return 'IndexCategory(%s, %s)' % (repr(self.name),
|
|
repr(self.items))
|
|
|
|
def draw(self, rtl, ctx, pc, layout, fascent, fheight,
|
|
baseline_x, baseline_y):
|
|
"""Draw this category header.
|
|
|
|
Args:
|
|
rtl (boolean): whether to draw right-to-left or not.
|
|
ctx (cairo.Context): the Cairo context to draw to.
|
|
pc (pangocairo.CairoContext): the PangoCairo context.
|
|
layout (pango.layout): the Pango layout to draw text into.
|
|
fascent (int): font ascent.
|
|
fheight (int): font height.
|
|
baseline_x (int): base X axis position.
|
|
baseline_y (int): base Y axis position.
|
|
"""
|
|
|
|
ctx.save()
|
|
ctx.set_source_rgb(0.9, 0.9, 0.9)
|
|
ctx.rectangle(baseline_x, baseline_y - fascent,
|
|
layout.get_width() / pango.SCALE, fheight)
|
|
ctx.fill()
|
|
|
|
ctx.set_source_rgb(0.0, 0.0, 0.0)
|
|
draw_utils.draw_text_center(ctx, pc, layout, fascent, fheight,
|
|
baseline_x, baseline_y, self.name)
|
|
ctx.restore()
|
|
|
|
def get_all_item_labels(self):
|
|
return [x.label for x in self.items]
|
|
|
|
def get_all_item_squares(self):
|
|
return [x.squares for x in self.items]
|
|
|
|
|
|
class IndexItem:
|
|
"""
|
|
An IndexItem represents one item in the index (a street or a POI). It
|
|
contains the item label (street name, POI name or description) and the
|
|
humanized squares description.
|
|
"""
|
|
__slots__ = ['label', 'endpoint1', 'endpoint2', 'location_str']
|
|
label = None # str
|
|
endpoint1 = None # coords.Point
|
|
endpoint2 = None # coords.Point
|
|
location_str = None # str or None
|
|
page_number = None # integer or None. Only used by multi-page renderer.
|
|
|
|
def __init__(self, label, endpoint1, endpoint2, page_number=None):
|
|
assert label is not None
|
|
self.label = label
|
|
self.endpoint1 = endpoint1
|
|
self.endpoint2 = endpoint2
|
|
self.location_str = None
|
|
self.page_number = page_number
|
|
|
|
def __str__(self):
|
|
return '%s...%s' % (self.label, self.location_str)
|
|
|
|
def __repr__(self):
|
|
return ('IndexItem(%s, %s, %s, %s, %s)'
|
|
% (repr(self.label), self.endpoint1, self.endpoint2,
|
|
repr(self.location_str), repr(self.page_number)))
|
|
|
|
def label_drawing_width(self, layout):
|
|
layout.set_text(self.label)
|
|
return float(layout.get_size()[0]) / pango.SCALE
|
|
|
|
def label_drawing_height(self, layout):
|
|
layout.set_text(self.label)
|
|
return float(layout.get_size()[1]) / pango.SCALE
|
|
|
|
def location_drawing_width(self, layout):
|
|
layout.set_text(self.location_str)
|
|
return float(layout.get_size()[0]) / pango.SCALE
|
|
|
|
def draw(self, rtl, ctx, pc, column_layout, fascent, fheight,
|
|
baseline_x, baseline_y,
|
|
label_layout=None, label_height=0, location_width=0):
|
|
"""Draw this index item to the provided Cairo context. It prints the
|
|
label, the squares definition and the dotted line, with respect to the
|
|
RTL setting.
|
|
|
|
Args:
|
|
rtl (boolean): right-to-left localization.
|
|
ctx (cairo.Context): the Cairo context to draw to.
|
|
pc (pangocairo.PangoCairo): the PangoCairo context for text
|
|
drawing.
|
|
column_layout (pango.Layout): the Pango layout to use for text
|
|
rendering, pre-configured with the appropriate font.
|
|
fascent (int): font ascent.
|
|
fheight (int): font height.
|
|
baseline_x (int): X axis coordinate of the baseline.
|
|
baseline_y (int): Y axis coordinate of the baseline.
|
|
Optional args (in case of label wrapping):
|
|
label_layout (pango.Layout): the Pango layout to use for text
|
|
rendering, in case the label should be wrapped
|
|
label_height (int): height of the big label
|
|
location_width (int): width of the 'location' part
|
|
"""
|
|
|
|
# Fallbacks in case we dont't have a wrapping label
|
|
if label_layout == None:
|
|
label_layout = column_layout
|
|
if label_height == 0:
|
|
label_height = fheight
|
|
|
|
if not self.location_str:
|
|
location_str = '???'
|
|
else:
|
|
location_str = self.location_str
|
|
|
|
ctx.save()
|
|
if not rtl:
|
|
_, _, line_start = draw_utils.draw_text_left(ctx, pc, label_layout,
|
|
fascent, fheight,
|
|
baseline_x, baseline_y,
|
|
self.label)
|
|
line_end, _, _ = draw_utils.draw_text_right(ctx, pc, column_layout,
|
|
fascent, fheight,
|
|
baseline_x, baseline_y,
|
|
location_str)
|
|
else:
|
|
_, _, line_start = draw_utils.draw_text_left(ctx, pc, column_layout,
|
|
fascent, fheight,
|
|
baseline_x, baseline_y,
|
|
location_str)
|
|
line_end, _, _ = draw_utils.draw_text_right(ctx, pc, label_layout,
|
|
fascent, fheight,
|
|
(baseline_x
|
|
+ location_width),
|
|
baseline_y,
|
|
self.label)
|
|
|
|
# In case of empty label, we don't draw the dots
|
|
if self.label != '':
|
|
draw_utils.draw_dotted_line(ctx, max(fheight/12, 1),
|
|
line_start + fheight/4, baseline_y,
|
|
line_end - line_start - fheight/2)
|
|
ctx.restore()
|
|
|
|
def update_location_str(self, grid):
|
|
"""
|
|
Update the location_str field from the given Grid object.
|
|
|
|
Args:
|
|
grid (ocitysmap.Grid): the Grid object from which we
|
|
compute the location strings
|
|
|
|
Returns:
|
|
Nothing, but the location_str field will have been altered
|
|
"""
|
|
if self.endpoint1 is not None:
|
|
ep1_label = grid.get_location_str( * self.endpoint1.get_latlong())
|
|
else:
|
|
ep1_label = None
|
|
if self.endpoint2 is not None:
|
|
ep2_label = grid.get_location_str( * self.endpoint2.get_latlong())
|
|
else:
|
|
ep2_label = None
|
|
if ep1_label is None:
|
|
ep1_label = ep2_label
|
|
if ep2_label is None:
|
|
ep2_label = ep1_label
|
|
|
|
if ep1_label == ep2_label:
|
|
if ep1_label is None:
|
|
self.location_str = "???"
|
|
self.location_str = ep1_label
|
|
elif grid.rtl:
|
|
self.location_str = "%s-%s" % (max(ep1_label, ep2_label),
|
|
min(ep1_label, ep2_label))
|
|
else:
|
|
self.location_str = "%s-%s" % (min(ep1_label, ep2_label),
|
|
max(ep1_label, ep2_label))
|
|
|
|
if self.page_number is not None:
|
|
if grid.rtl:
|
|
self.location_str = "%s, %d" % (self.location_str,
|
|
self.page_number)
|
|
else:
|
|
self.location_str = "%d, %s" % (self.page_number,
|
|
self.location_str)
|
|
|
|
if __name__ == "__main__":
|
|
import cairo
|
|
import pangocairo
|
|
|
|
surface = cairo.PDFSurface('/tmp/idx_commons.pdf', 1000, 1000)
|
|
|
|
ctx = cairo.Context(surface)
|
|
pc = pangocairo.CairoContext(ctx)
|
|
|
|
font_desc = pango.FontDescription('DejaVu')
|
|
font_desc.set_size(12 * pango.SCALE)
|
|
|
|
layout = pc.create_layout()
|
|
layout.set_font_description(font_desc)
|
|
layout.set_width(200 * pango.SCALE)
|
|
|
|
font = layout.get_context().load_font(font_desc)
|
|
font_metric = font.get_metrics()
|
|
|
|
fascent = font_metric.get_ascent() / pango.SCALE
|
|
fheight = ((font_metric.get_ascent() + font_metric.get_descent())
|
|
/ pango.SCALE)
|
|
|
|
first_item = IndexItem('First Item', None, None)
|
|
second_item = IndexItem('Second Item', None, None)
|
|
category = IndexCategory('Hello world !', [first_item, second_item])
|
|
|
|
category.draw(False, ctx, pc, layout, fascent, fheight,
|
|
72, 80)
|
|
first_item.draw(False, ctx, pc, layout, fascent, fheight,
|
|
72, 100)
|
|
second_item.draw(False, ctx, pc, layout, fascent, fheight,
|
|
72, 120)
|
|
|
|
surface.finish()
|
|
print "Generated /tmp/idx_commons.pdf"
|