def render(renderer, ctx): svg_path = os.path.abspath( os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', 'images', 'compass-rose.svg')) if not os.path.exists(svg_path): logo_path = os.path.join(sys.exec_prefix, 'share', 'images', 'ocitysmap', 'compass-rose.svg') if not os.path.exists(svg_path): LOG.warning("No compass rose image found") return h = convert_pt_to_dots(0.05 * renderer.paper_height_pt, renderer.dpi) if type(renderer).__name__ == "MultiPageRenderer": x = 0 y = 0 else: x = convert_pt_to_dots(renderer._map_coords[0], renderer.dpi) y = convert_pt_to_dots(renderer._map_coords[1], renderer.dpi) ctx.save() ctx.translate(x + h / 2, y + h / 2) rose_grp, rose_width = Renderer._get_svg(ctx, svg_path, h) ctx.set_source(rose_grp) ctx.paint_with_alpha(0.75) ctx.stroke() ctx.restore()
def __init__(self, stylesheet, bounding_box, _width, _height, dpi=72.0, extend_bbox_to_ratio=True): """Initialize the map canvas for rendering. Args: stylesheet (Stylesheet): map stylesheet. bounding_box (coords.BoundingBox): geographic bounding box. graphical_ratio (float): ratio of the map area (width/height). dpi (float): map resolution (default: 72dpi) extend_bbox_to_ratio (boolean): allow MapCanvas to extend the bounding box to make it match the ratio of the provided rendering area. Needed by SinglePageRenderer. """ self._proj = mapnik.Projection(_MAPNIK_PROJECTION) self._dpi = dpi # This is where the magic of the map canvas happens. Given an original # bounding box and a graphical ratio for the output, the bounding box # is adjusted (extended) to fill the destination zone. See # _fix_bbox_ratio for more details on how this is done. orig_envelope = self._project_envelope(bounding_box) graphical_ratio = _width / _height if extend_bbox_to_ratio: off_x, off_y, width, height = self._fix_bbox_ratio( orig_envelope.minx, orig_envelope.miny, orig_envelope.width(), orig_envelope.height(), graphical_ratio) envelope = mapnik.Box2d(off_x, off_y, off_x + width, off_y + height) self._geo_bbox = self._inverse_envelope(envelope) LOG.debug('Corrected bounding box from %s to %s, ratio: %.2f.' % (bounding_box, self._geo_bbox, graphical_ratio)) else: envelope = orig_envelope self._geo_bbox = bounding_box g_width = int(convert_pt_to_dots(_width, dpi)) g_height = int(convert_pt_to_dots(_height, dpi)) # Create the Mapnik map with the corrected width and height and zoom to # the corrected bounding box ('envelope' in the Mapnik jargon) self._map = mapnik.Map(g_width, g_height, _MAPNIK_PROJECTION) mapnik.load_map(self._map, stylesheet.path) self._map.zoom_to_box(envelope) # Added shapes to render self._shapes = [] LOG.debug('MapCanvas rendering map on %dx%dpx.' % (g_width, g_height))
def render_page_number(ctx, page_number, usable_area_width_pt, usable_area_height_pt, margin_pt, transparent_background = True): """ Render page number """ ctx.save() x_offset = 0 if page_number % 2: x_offset += commons.convert_pt_to_dots(usable_area_width_pt)\ - commons.convert_pt_to_dots(margin_pt) y_offset = commons.convert_pt_to_dots(usable_area_height_pt)\ - commons.convert_pt_to_dots(margin_pt) ctx.translate(x_offset, y_offset) if transparent_background: ctx.set_source_rgba(1, 1, 1, 0.6) else: ctx.set_source_rgba(0.8, 0.8, 0.8, 0.6) ctx.rectangle(0, 0, commons.convert_pt_to_dots(margin_pt), commons.convert_pt_to_dots(margin_pt)) ctx.fill() ctx.set_source_rgba(0, 0, 0, 1) x_offset = commons.convert_pt_to_dots(margin_pt)/2 y_offset = commons.convert_pt_to_dots(margin_pt)/2 ctx.translate(x_offset, y_offset) draw_simpletext_center(ctx, unicode(page_number), 0, 0) ctx.restore()
def render_page_number(ctx, page_number, usable_area_width_pt, usable_area_height_pt, margin_pt, transparent_background=True): """ Render page number """ ctx.save() x_offset = 0 if page_number % 2: x_offset += commons.convert_pt_to_dots(usable_area_width_pt)\ - commons.convert_pt_to_dots(margin_pt) y_offset = commons.convert_pt_to_dots(usable_area_height_pt)\ - commons.convert_pt_to_dots(margin_pt) ctx.translate(x_offset, y_offset) if transparent_background: ctx.set_source_rgba(1, 1, 1, 0.6) else: ctx.set_source_rgba(0.8, 0.8, 0.8, 0.6) ctx.rectangle(0, 0, commons.convert_pt_to_dots(margin_pt), commons.convert_pt_to_dots(margin_pt)) ctx.fill() ctx.set_source_rgba(0, 0, 0, 1) x_offset = commons.convert_pt_to_dots(margin_pt) / 2 y_offset = commons.convert_pt_to_dots(margin_pt) / 2 ctx.translate(x_offset, y_offset) draw_simpletext_center(ctx, unicode(page_number), 0, 0) ctx.restore()
def render(renderer, ctx): if renderer.rc.qrcode_text: qrcode_text = renderer.rc.qrcode_text else: qrcode_text = renderer.rc.origin_url if not qrcode_text: return x = convert_pt_to_dots(renderer._map_coords[0], renderer.dpi) y = convert_pt_to_dots(renderer._map_coords[1], renderer.dpi) w = convert_pt_to_dots(renderer._map_coords[2], renderer.dpi) h = convert_pt_to_dots(renderer._map_coords[3], renderer.dpi) W = convert_pt_to_dots(renderer.paper_width_pt) size = convert_pt_to_dots( max(renderer.paper_width_pt, renderer.paper_height_pt), renderer.dpi) / 12 qr = qrcode.QRCode( version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=10, border=4, ) qr.add_data(qrcode_text) qr.make(fit=True) img = qr.make_image(image_factory=qrcode.image.svg.SvgPathFillImage, fill_color='lightblue') svgstr = BytesIO() img.save(svgstr) svg_val = svgstr.getvalue() rsvg = Rsvg.Handle() svg = rsvg.new_from_data(svg_val) svgstr.close() ctx.save() ctx.translate(x + w - size, y + h - size) ctx.move_to(0, 0) factor = size / svg.props.height ctx.scale(factor, factor) svg.render_cairo(ctx) ctx.restore()
def render(self, dpi = UTILS.PT_PER_INCH): self.ctx.save() # Create a PangoCairo context for drawing to Cairo pc = pangocairo.CairoContext(self.ctx) header_fd = pango.FontDescription("Georgia Bold 12") label_column_fd = pango.FontDescription("DejaVu 8") header_layout, header_fascent, header_fheight, header_em = \ self._create_layout_with_font(pc, header_fd) label_layout, label_fascent, label_fheight, label_em = \ self._create_layout_with_font(pc, label_column_fd) column_layout, _, _, _ = \ self._create_layout_with_font(pc, label_column_fd) # By OCitysmap's convention, the default resolution is 72 dpi, # which maps to the default pangocairo resolution (96 dpi # according to pangocairo docs). If we want to render with # another resolution (different from 72), we have to scale the # pangocairo resolution accordingly: pangocairo.context_set_resolution(column_layout.get_context(), 96.*dpi/UTILS.PT_PER_INCH) pangocairo.context_set_resolution(label_layout.get_context(), 96.*dpi/UTILS.PT_PER_INCH) pangocairo.context_set_resolution(header_layout.get_context(), 96.*dpi/UTILS.PT_PER_INCH) margin = label_em # find largest label and location max_label_drawing_width = 0.0 max_location_drawing_width = 0.0 for category in self.index_categories: for street in category.items: w = street.label_drawing_width(label_layout) if w > max_label_drawing_width: max_label_drawing_width = w w = street.location_drawing_width(label_layout) if w > max_location_drawing_width: max_location_drawing_width = w # No street to render, bail out if max_label_drawing_width == 0.0: return # Find best number of columns max_drawing_width = \ max_label_drawing_width + max_location_drawing_width + 2 * margin max_drawing_height = self.rendering_area_h - PAGE_NUMBER_MARGIN_PT columns_count = int(math.ceil(self.rendering_area_w / max_drawing_width)) # following test should not be needed. No time to prove it. ;-) if columns_count == 0: columns_count = 1 # We have now have several columns column_width = self.rendering_area_w / columns_count column_layout.set_width(int(UTILS.convert_pt_to_dots( (column_width - margin) * pango.SCALE, dpi))) label_layout.set_width(int(UTILS.convert_pt_to_dots( (column_width - margin - max_location_drawing_width - 2 * label_em) * pango.SCALE, dpi))) header_layout.set_width(int(UTILS.convert_pt_to_dots( (column_width - margin) * pango.SCALE, dpi))) if not self._i18n.isrtl(): orig_offset_x = offset_x = margin/2. orig_delta_x = delta_x = column_width else: orig_offset_x = offset_x = \ self.rendering_area_w - column_width + margin/2. orig_delta_x = delta_x = - column_width actual_n_cols = 0 offset_y = margin/2. # page number of first page self._draw_page_number() for category in self.index_categories: if ( offset_y + header_fheight + label_fheight + margin/2. > max_drawing_height ): offset_y = margin/2. offset_x += delta_x actual_n_cols += 1 if actual_n_cols == columns_count: self._new_page() actual_n_cols = 0 offset_y = margin / 2. offset_x = orig_offset_x delta_x = orig_delta_x category.draw(self._i18n.isrtl(), self.ctx, pc, header_layout, UTILS.convert_pt_to_dots(header_fascent, dpi), UTILS.convert_pt_to_dots(header_fheight, dpi), UTILS.convert_pt_to_dots(self.rendering_area_x + offset_x, dpi), UTILS.convert_pt_to_dots(self.rendering_area_y + offset_y + header_fascent, dpi)) offset_y += header_fheight for street in category.items: label_height = street.label_drawing_height(label_layout) if ( offset_y + label_height + margin/2. > max_drawing_height ): offset_y = margin/2. offset_x += delta_x actual_n_cols += 1 if actual_n_cols == columns_count: self._new_page() actual_n_cols = 0 offset_y = margin / 2. offset_x = orig_offset_x delta_x = orig_delta_x street.draw(self._i18n.isrtl(), self.ctx, pc, column_layout, UTILS.convert_pt_to_dots(label_fascent, dpi), UTILS.convert_pt_to_dots(label_fheight, dpi), UTILS.convert_pt_to_dots(self.rendering_area_x + offset_x, dpi), UTILS.convert_pt_to_dots(self.rendering_area_y + offset_y + label_fascent, dpi), label_layout, UTILS.convert_pt_to_dots(label_height, dpi), UTILS.convert_pt_to_dots(max_location_drawing_width, dpi)) offset_y += label_height self.ctx.restore()
def render(self, cairo_surface, dpi, osm_date): """Renders the map, the index and all other visual map features on the given Cairo surface. Args: cairo_surface (Cairo.Surface): the destination Cairo device. dpi (int): dots per inch of the device. """ LOG.info('SinglePageRenderer rendering -%s- on %dx%dmm paper at %d dpi.' % (self.rc.output_format, self.rc.paper_width_mm, self.rc.paper_height_mm, dpi)) # First determine some useful drawing parameters safe_margin_dots \ = commons.convert_pt_to_dots(Renderer.PRINT_SAFE_MARGIN_PT, dpi) usable_area_width_dots \ = commons.convert_pt_to_dots(self._usable_area_width_pt, dpi) usable_area_height_dots \ = commons.convert_pt_to_dots(self._usable_area_height_pt, dpi) title_margin_dots \ = commons.convert_pt_to_dots(self._title_margin_pt, dpi) copyright_margin_dots \ = commons.convert_pt_to_dots(self._copyright_margin_pt, dpi) map_coords_dots = list(map(lambda l: commons.convert_pt_to_dots(l, dpi), self._map_coords)) ctx = cairo.Context(cairo_surface) # Set a white background ctx.save() ctx.set_source_rgb(1, 1, 1) ctx.rectangle(0, 0, commons.convert_pt_to_dots(self.paper_width_pt, dpi), commons.convert_pt_to_dots(self.paper_height_pt, dpi)) ctx.fill() ctx.restore() ## ## Draw the map, scaled to fit the designated area ## ctx.save() # prevent map background from filling the full canvas ctx.rectangle(map_coords_dots[0], map_coords_dots[1], map_coords_dots[2], map_coords_dots[3]) ctx.clip() # Prepare to draw the map at the right location ctx.translate(map_coords_dots[0], map_coords_dots[1]) # Draw the rescaled Map ctx.save() scale_factor = int(dpi / 72) rendered_map = self._map_canvas.get_rendered_map() LOG.debug('Map:') LOG.debug('Mapnik scale: 1/%f' % rendered_map.scale_denominator()) LOG.debug('Actual scale: 1/%f' % self._map_canvas.get_actual_scale()) # exclude layers based on configuration setting "exclude_layers" for layer in rendered_map.layers: if layer.name in self.rc.stylesheet.exclude_layers: LOG.debug("Excluding layer: %s" % layer.name) layer.status = False # now perform the actual drawing mapnik.render(rendered_map, ctx, scale_factor, 0, 0) ctx.restore() # Draw the rescaled Overlays for overlay_canvas in self._overlay_canvases: ctx.save() rendered_overlay = overlay_canvas.get_rendered_map() LOG.debug('Overlay:') # TODO: overlay name mapnik.render(rendered_overlay, ctx, scale_factor, 0, 0) ctx.restore() # Place the vertical and horizontal square labels if self.grid and self.index_position: self._draw_labels(ctx, self.grid, map_coords_dots[2], map_coords_dots[3], commons.convert_pt_to_dots(self._grid_legend_margin_pt, dpi)) ctx.restore() # Draw a rectangle frame around the map ctx.save() ctx.set_line_width(1) ctx.rectangle(map_coords_dots[0], map_coords_dots[1], map_coords_dots[2], map_coords_dots[3]) ctx.stroke() ctx.restore() ## ## Draw the title ## if self.rc.title: ctx.save() ctx.translate(safe_margin_dots, safe_margin_dots) self._draw_title(ctx, usable_area_width_dots, title_margin_dots, 'Droid Sans Bold') ctx.restore() # make sure that plugins do not render outside the actual map area ctx.save() ctx.rectangle(map_coords_dots[0], map_coords_dots[1], map_coords_dots[2], map_coords_dots[3]) ctx.clip() # apply effect plugin overlays for plugin_name, effect in self._overlay_effects.items(): try: effect.render(self, ctx) except Exception as e: # TODO better logging LOG.warning("Error while rendering overlay: %s\n%s" % (plugin_name, e)) ctx.restore() ## ## Draw the index, when applicable ## # Update the street_index to reflect the grid's actual position if self.grid and self.street_index and self.index_position is not None: self.street_index.apply_grid(self.grid) # Dump the CSV street index self.street_index.write_to_csv(self.rc.title, '%s.csv' % self.file_prefix) if self._index_renderer and self._index_area: ctx.save() # NEVER use ctx.scale() here because otherwise pango will # choose different dont metrics which may be incompatible # with what has been computed by __init__(), which may # require more columns than expected ! Instead, we have # to trick pangocairo into believing it is rendering to a # device with the same default resolution, but with a # cairo resolution matching the 'dpi' specified # resolution. See # index::render::StreetIndexRenederer::render() and # comments within. self._index_renderer.render(ctx, self._index_area, dpi) ctx.restore() # Also draw a rectangle frame around the index ctx.save() ctx.set_line_width(1) ctx.rectangle(commons.convert_pt_to_dots(self._index_area.x, dpi), commons.convert_pt_to_dots(self._index_area.y, dpi), commons.convert_pt_to_dots(self._index_area.w, dpi), commons.convert_pt_to_dots(self._index_area.h, dpi)) ctx.stroke() ctx.restore() ## ## Draw the copyright notice ## ctx.save() # Move to the right position ctx.translate(safe_margin_dots, ( safe_margin_dots + title_margin_dots + usable_area_height_dots + copyright_margin_dots/4. ) ) # Draw the copyright notice self._draw_copyright_notice(ctx, usable_area_width_dots, copyright_margin_dots, osm_date=osm_date) ctx.restore() # render index on 2nd page if requested, and output format supports it if self.index_position == 'extra_page' and self._has_multipage_format() and self._index_renderer is not None: cairo_surface.show_page() # We use a fake vector device to determine the actual # rendering characteristics fake_surface = cairo.PDFSurface(None, self.paper_width_pt, self.paper_height_pt) usable_area_width_pt = (self.paper_width_pt - 2 * Renderer.PRINT_SAFE_MARGIN_PT) usable_area_height_pt = (self.paper_height_pt - 2 * Renderer.PRINT_SAFE_MARGIN_PT) index_area = self._index_renderer.precompute_occupation_area( fake_surface, Renderer.PRINT_SAFE_MARGIN_PT, ( self.paper_height_pt - Renderer.PRINT_SAFE_MARGIN_PT - usable_area_height_pt ), usable_area_width_pt, usable_area_height_pt, 'width', 'left') ctx.save() self._index_renderer.render(ctx, index_area, dpi) ctx.restore() cairo_surface.show_page() else: cairo_surface.flush()
def render(self, dpi=UTILS.PT_PER_INCH): self.ctx.save() # Create a PangoCairo context for drawing to Cairo pc = pangocairo.CairoContext(self.ctx) header_fd = pango.FontDescription("Georgia Bold 12") label_column_fd = pango.FontDescription("DejaVu 8") header_layout, header_fascent, header_fheight, header_em = \ self._create_layout_with_font(pc, header_fd) label_layout, label_fascent, label_fheight, label_em = \ self._create_layout_with_font(pc, label_column_fd) column_layout, _, _, _ = \ self._create_layout_with_font(pc, label_column_fd) # By OCitysmap's convention, the default resolution is 72 dpi, # which maps to the default pangocairo resolution (96 dpi # according to pangocairo docs). If we want to render with # another resolution (different from 72), we have to scale the # pangocairo resolution accordingly: pangocairo.context_set_resolution(column_layout.get_context(), 96. * dpi / UTILS.PT_PER_INCH) pangocairo.context_set_resolution(label_layout.get_context(), 96. * dpi / UTILS.PT_PER_INCH) pangocairo.context_set_resolution(header_layout.get_context(), 96. * dpi / UTILS.PT_PER_INCH) margin = label_em # find largest label and location max_label_drawing_width = 0.0 max_location_drawing_width = 0.0 for category in self.index_categories: for street in category.items: w = street.label_drawing_width(label_layout) if w > max_label_drawing_width: max_label_drawing_width = w w = street.location_drawing_width(label_layout) if w > max_location_drawing_width: max_location_drawing_width = w # No street to render, bail out if max_label_drawing_width == 0.0: return # Find best number of columns max_drawing_width = \ max_label_drawing_width + max_location_drawing_width + 2 * margin max_drawing_height = self.rendering_area_h - PAGE_NUMBER_MARGIN_PT columns_count = int( math.ceil(self.rendering_area_w / max_drawing_width)) # following test should not be needed. No time to prove it. ;-) if columns_count == 0: columns_count = 1 # We have now have several columns column_width = self.rendering_area_w / columns_count column_layout.set_width( int( UTILS.convert_pt_to_dots((column_width - margin) * pango.SCALE, dpi))) label_layout.set_width( int( UTILS.convert_pt_to_dots( (column_width - margin - max_location_drawing_width - 2 * label_em) * pango.SCALE, dpi))) header_layout.set_width( int( UTILS.convert_pt_to_dots((column_width - margin) * pango.SCALE, dpi))) if not self._i18n.isrtl(): orig_offset_x = offset_x = margin / 2. orig_delta_x = delta_x = column_width else: orig_offset_x = offset_x = \ self.rendering_area_w - column_width + margin/2. orig_delta_x = delta_x = -column_width actual_n_cols = 0 offset_y = margin / 2. # page number of first page self._draw_page_number() for category in self.index_categories: if (offset_y + header_fheight + label_fheight + margin / 2. > max_drawing_height): offset_y = margin / 2. offset_x += delta_x actual_n_cols += 1 if actual_n_cols == columns_count: self._new_page() actual_n_cols = 0 offset_y = margin / 2. offset_x = orig_offset_x delta_x = orig_delta_x category.draw( self._i18n.isrtl(), self.ctx, pc, header_layout, UTILS.convert_pt_to_dots(header_fascent, dpi), UTILS.convert_pt_to_dots(header_fheight, dpi), UTILS.convert_pt_to_dots(self.rendering_area_x + offset_x, dpi), UTILS.convert_pt_to_dots( self.rendering_area_y + offset_y + header_fascent, dpi)) offset_y += header_fheight for street in category.items: label_height = street.label_drawing_height(label_layout) if (offset_y + label_height + margin / 2. > max_drawing_height): offset_y = margin / 2. offset_x += delta_x actual_n_cols += 1 if actual_n_cols == columns_count: self._new_page() actual_n_cols = 0 offset_y = margin / 2. offset_x = orig_offset_x delta_x = orig_delta_x street.draw( self._i18n.isrtl(), self.ctx, pc, column_layout, UTILS.convert_pt_to_dots(label_fascent, dpi), UTILS.convert_pt_to_dots(label_fheight, dpi), UTILS.convert_pt_to_dots(self.rendering_area_x + offset_x, dpi), UTILS.convert_pt_to_dots( self.rendering_area_y + offset_y + label_fascent, dpi), label_layout, UTILS.convert_pt_to_dots(label_height, dpi), UTILS.convert_pt_to_dots(max_location_drawing_width, dpi)) offset_y += label_height self.ctx.restore()
def render(self, ctx, rendering_area, dpi = UTILS.PT_PER_INCH): """ Render the street and amenities index at the given (x,y) coordinates into the provided Cairo surface. The index must not be larger than the provided surface (use precompute_occupation_area() to adjust it). Args: ctx (cairo.Context): the cairo context to use for the rendering. rendering_area (StreetIndexRenderingArea): the result from precompute_occupation_area(). dpi (number): resolution of the target device. """ if not self._index_categories: raise commons.IndexEmptyError LOG.debug("Rendering the street index within %s at %sdpi..." % (rendering_area, dpi)) ## ## In the following, the algorithm only manipulates values ## expressed in 'pt'. Only the drawing-related functions will ## translate them to cairo units ## ctx.save() ctx.move_to(UTILS.convert_pt_to_dots(rendering_area.x, dpi), UTILS.convert_pt_to_dots(rendering_area.y, dpi)) # Create a PangoCairo context for drawing to Cairo pc = pangocairo.CairoContext(ctx) header_fd = pango.FontDescription( rendering_area.rendering_style.header_font_spec) label_fd = pango.FontDescription( rendering_area.rendering_style.label_font_spec) header_layout, header_fascent, header_fheight, header_em = \ self._create_layout_with_font(pc, header_fd) label_layout, label_fascent, label_fheight, label_em = \ self._create_layout_with_font(pc, label_fd) #print "RENDER", header_layout, header_fascent, header_fheight, header_em #print "RENDER", label_layout, label_fascent, label_fheight, label_em # By OCitysmap's convention, the default resolution is 72 dpi, # which maps to the default pangocairo resolution (96 dpi # according to pangocairo docs). If we want to render with # another resolution (different from 72), we have to scale the # pangocairo resolution accordingly: pangocairo.context_set_resolution(label_layout.get_context(), 96.*dpi/UTILS.PT_PER_INCH) pangocairo.context_set_resolution(header_layout.get_context(), 96.*dpi/UTILS.PT_PER_INCH) # All this is because we want pango to have the exact same # behavior as with the default 72dpi resolution. If we instead # decided to call cairo::scale, then pango might choose # different font metrics which don't fit in the prepared # layout anymore... margin = label_em column_width = int(rendering_area.w / rendering_area.n_cols) label_layout.set_width(int(UTILS.convert_pt_to_dots( (column_width - margin) * pango.SCALE, dpi))) header_layout.set_width(int(UTILS.convert_pt_to_dots( (column_width - margin) * pango.SCALE, dpi))) if not self._i18n.isrtl(): offset_x = margin/2. delta_x = column_width else: offset_x = rendering_area.w - column_width + margin/2. delta_x = - column_width actual_n_cols = 1 offset_y = margin/2. for category in self._index_categories: if ( offset_y + header_fheight + label_fheight + margin/2. > rendering_area.h ): offset_y = margin/2. offset_x += delta_x actual_n_cols += 1 category.draw(self._i18n.isrtl(), ctx, pc, header_layout, UTILS.convert_pt_to_dots(header_fascent, dpi), UTILS.convert_pt_to_dots(header_fheight, dpi), UTILS.convert_pt_to_dots(rendering_area.x + offset_x, dpi), UTILS.convert_pt_to_dots(rendering_area.y + offset_y + header_fascent, dpi)) offset_y += header_fheight for street in category.items: if ( offset_y + label_fheight + margin/2. > rendering_area.h ): offset_y = margin/2. offset_x += delta_x actual_n_cols += 1 street.draw(self._i18n.isrtl(), ctx, pc, label_layout, UTILS.convert_pt_to_dots(label_fascent, dpi), UTILS.convert_pt_to_dots(label_fheight, dpi), UTILS.convert_pt_to_dots(rendering_area.x + offset_x, dpi), UTILS.convert_pt_to_dots(rendering_area.y + offset_y + label_fascent, dpi)) offset_y += label_fheight # Restore original context ctx.restore() # Simple verification... if actual_n_cols < rendering_area.n_cols: LOG.warning("Rounding/security margin lost some space (%d actual cols vs. allocated %d" % (actual_n_cols, rendering_area.n_cols)) assert actual_n_cols <= rendering_area.n_cols
def render(self, dpi=UTILS.PT_PER_INCH): self.ctx.save() # Create a PangoCairo context for drawing to Cairo pc = PangoCairo.create_context(self.ctx) city_fd = Pango.FontDescription("DejaVu Sans Condensed Bold 18") header_fd = Pango.FontDescription("DejaVu Sans Condensed Bold 12") label_column_fd = Pango.FontDescription("DejaVu 6") city_layout, city_fascent, city_fheight, city_em = \ self._create_layout_with_font(self.ctx, pc, city_fd) header_layout, header_fascent, header_fheight, header_em = \ self._create_layout_with_font(self.ctx, pc, header_fd) label_layout, label_fascent, label_fheight, label_em = \ self._create_layout_with_font(self.ctx, pc, label_column_fd) column_layout, _, _, _ = \ self._create_layout_with_font(self.ctx, pc, label_column_fd) # By OCitysmap's convention, the default resolution is 72 dpi, # which maps to the default pangocairo resolution (96 dpi # according to pangocairo docs). If we want to render with # another resolution (different from 72), we have to scale the # pangocairo resolution accordingly: PangoCairo.context_set_resolution(city_layout.get_context(), 96. * dpi / UTILS.PT_PER_INCH) PangoCairo.context_set_resolution(column_layout.get_context(), 96. * dpi / UTILS.PT_PER_INCH) PangoCairo.context_set_resolution(label_layout.get_context(), 96. * dpi / UTILS.PT_PER_INCH) PangoCairo.context_set_resolution(header_layout.get_context(), 96. * dpi / UTILS.PT_PER_INCH) margin = label_em cityBlockHeight = city_fheight * 2 index_area_w_pt = self.rendering_area_w - 2 * self.print_bleed_pt - self.margin_inside_pt - self.margin_outside_pt city_layout.set_width( int(UTILS.convert_pt_to_dots((index_area_w_pt) * Pango.SCALE, dpi))) self._draw_page_stroke() self._draw_page_content_stroke() citiesWithEntries = { k: v for k, v in self.index_categories.items() if len(v) > 0 } # print only cities with entries cities = list(citiesWithEntries.keys()) cities.sort() margin_top = self.print_bleed_pt + self.margin_top_bottom_pt margin_top_page = margin_top offset_y = margin_top_page max_drawing_height = 0 city_index = -1 LOG.debug( "%f print_bleed_pt, %f print_safe_margin_pt, %f margin_top_bottom_pt, %f margin_top" % (self.print_bleed_pt, self.print_safe_margin_pt, self.margin_top_bottom_pt, margin_top)) page_full_available_h = self.rendering_area_h - 2 * self.print_bleed_pt - 2 * self.margin_top_bottom_pt content_width = self.rendering_area_w - 2 * Renderer.PRINT_BLEED_PT content_height = self.rendering_area_h - 2 * Renderer.PRINT_BLEED_PT margin_x = self.print_bleed_pt margin_y = self.print_bleed_pt if Renderer.DEBUG: # red stroke dash1: show area excluding bleed-difference self.ctx.save() self.ctx.set_source_rgba(1, 0.4, 0.4, .75) self.ctx.set_dash([1.0, 1.0], 1.0 / 2.0) self.ctx.rectangle(margin_x, margin_y, content_width, content_height) self.ctx.stroke() self.ctx.restore() content_width -= self.margin_inside_pt + self.margin_outside_pt content_height -= 2 * self.margin_top_bottom_pt margin_x += (self.margin_inside_pt if (self.index_page_num + self.page_offset) % 2 else self.margin_outside_pt) margin_y += self.margin_top_bottom_pt #LOG.debug(list(filter(lambda x: len(self.index_categories[cities[x]]) > 0, cities))) for city in cities: city_index = city_index + 1 margin_top_page += max_drawing_height # add max drawing height of previous city index_area_h_pt = self.rendering_area_h - self.print_bleed_pt - self.margin_top_bottom_pt - margin_top_page LOG.debug("============") LOG.debug( "printing index for city '%s'. available area: %f x %f, margin_top_page: %f" % (city, index_area_w_pt, index_area_h_pt, margin_top_page)) if (margin_top_page > (page_full_available_h * 4 / 5)): LOG.debug("NEW PAGE: margin_top_page (%f) > %f" % (margin_top_page, page_full_available_h * 4 / 5)) self._new_page() self._draw_page_stroke() self._draw_page_content_stroke() margin_top = self.print_bleed_pt + self.margin_top_bottom_pt margin_top_page = margin_top index_area_h_pt = self.rendering_area_h - self.print_bleed_pt - self.margin_top_bottom_pt - margin_top_page # full page height available now with this new page. city_header_height = 0 if len(cities) > 1: city_header_height = self._draw_page_header( self._i18n.isrtl(), self.ctx, pc, city_layout, UTILS.convert_pt_to_dots(city_fascent, dpi), UTILS.convert_pt_to_dots(cityBlockHeight, dpi), UTILS.convert_pt_to_dots( self.rendering_area_x + self.print_bleed_pt + (self.margin_inside_pt if (self.index_page_num + self.page_offset) % 2 else self.margin_outside_pt), dpi), UTILS.convert_pt_to_dots( margin_top_page + header_fascent / 2, dpi ), # baseline_y, original: self.rendering_area_y + header_fascent margin_top_page, #margin_top city) index_area_h_pt -= city_header_height margin_top_page += city_header_height # find largest label and location max_label_drawing_width = 0.0 max_location_drawing_width = 0.0 if False: self.index_categories[city].append( self.index_categories[city][0]) self.index_categories[city].append( self.index_categories[city][0]) self.index_categories[city].append( self.index_categories[city][0]) self.index_categories[city].append( self.index_categories[city][0]) self.index_categories[city].append( self.index_categories[city][0]) self.index_categories[city].append( self.index_categories[city][0]) self.index_categories[city].append( self.index_categories[city][0]) self.index_categories[city].append( self.index_categories[city][0]) self.index_categories[city].append( self.index_categories[city][0]) self.index_categories[city].append( self.index_categories[city][0]) self.index_categories[city].append( self.index_categories[city][0]) self.index_categories[city].append( self.index_categories[city][0]) self.index_categories[city].append( self.index_categories[city][0]) if False: # Alle Kategorien, bis auf 1 entfernen while len(self.index_categories[city]) > 1: self.index_categories[city].pop(1) # Alle Einträge, bis auf 1, dieser Kategorie entferne #while len(self.index_categories[city][0].items)>4: # self.index_categories[city][0].items.pop(1) # Bei dieser Kategorie weitere Einträge hinzufügen for i in range(40): self.index_categories[city][0].items.append( self.index_categories[city][0].items[3]) # calculate height of categories #LOG.debug("number of categories: %d" % len(self.index_categories[city])) #LOG.debug("number of entries in first category: %d" % len(self.index_categories[city][0].items)) sum_height = 0 for category in self.index_categories[city]: sum_height += category.label_drawing_height(header_layout) #LOG.debug("adding height %f for category %s" % (category.label_drawing_height(header_layout), category.name)) for street in category.items: #LOG.debug("label_drawing_height of %s: %f" % (street.label, street.label_drawing_height(label_layout))) sum_height += street.label_drawing_height(label_layout) w = street.label_drawing_width(label_layout) if w > max_label_drawing_width: max_label_drawing_width = w #LOG.debug("new max_label_drawing_width: %f (%s)" % (max_label_drawing_width, street.label)) w = street.location_drawing_width(label_layout) if w > max_location_drawing_width: max_location_drawing_width = w #LOG.debug("new max_location_drawing_width: %f (%s)" % (max_location_drawing_width, street.location_str)) col_max_height = math.ceil(float(sum_height) / 4) + 40 LOG.debug( "sum_height: %f, %f per column (4) => col_max_height: %d" % (sum_height, float(sum_height) / 4, col_max_height)) # No street to render, bail out if max_label_drawing_width == 0.0: return #LOG.debug("max_label_drawing_width: %f" % max_label_drawing_width) #LOG.debug("max_location_drawing_width: %f" % max_location_drawing_width) # Find best number of columns # max_drawing_width = max_label_drawing_width + max_location_drawing_width + 2 * margin max_drawing_height = col_max_height needed_drawing_height = max_drawing_height if (index_area_h_pt < max_drawing_height): LOG.debug( "more height neededed (max_drawing_height: %f), than there is available on this page left (index_area_h_pt: %f). Setting max_drawing_height to index_area_h_pt" % (max_drawing_height, index_area_h_pt)) max_drawing_height = index_area_h_pt if Renderer.DEBUG: # green stroke dash3: show printable page area (after header) LOG.debug("Index - printable area (after header): %f x %f" % (index_area_w_pt, max_drawing_height)) self.ctx.save() self.ctx.set_source_rgba(0, 1, 0, .75) self.ctx.set_dash([3.0, 3.0], 3.0 / 2.0) self.ctx.rectangle( self.print_bleed_pt + (self.margin_inside_pt if (self.index_page_num + self.page_offset) % 2 else self.margin_outside_pt), margin_top_page, index_area_w_pt, max_drawing_height) self.ctx.stroke() self.ctx.restore() #LOG.debug("max_drawing_width: %f" % max_drawing_width) #columns_count = int(math.ceil(index_area_w_pt / max_drawing_width)) # following test should not be needed. No time to prove it. ;-) #if columns_count == 0: # columns_count = 1 #LOG.debug("number of columns: %d" % columns_count) columns_count = 4 # Gerald: fixed to 4. # We have now have several columns column_width = index_area_w_pt / columns_count #LOG.debug("column_width: %d" % column_width) column_layout.set_width( int( UTILS.convert_pt_to_dots( (column_width - margin - 5) * Pango.SCALE, dpi))) label_layout.set_width( int( UTILS.convert_pt_to_dots( (column_width - margin - max_location_drawing_width - 2 * label_em) * Pango.SCALE, dpi))) header_layout.set_width( int( UTILS.convert_pt_to_dots( (column_width - margin) * Pango.SCALE, dpi))) if not self._i18n.isrtl(): orig_offset_x = offset_x = margin / 2. orig_delta_x = delta_x = column_width else: orig_offset_x = offset_x = index_area_w_pt - column_width + margin / 2. orig_delta_x = delta_x = -column_width actual_n_cols = 0 # page number of first page self._draw_page_number() if Renderer.DEBUG: # light pink stroke dash 4: full column # temp: show index-area (inside grayed margin) LOG.debug( "pink: %f w -> index_area_w_pt, %f h -> index_area_h_pt" % (index_area_w_pt, index_area_h_pt)) self.ctx.save() self.ctx.set_source_rgba(.85, .25, .85, .75) self.ctx.set_dash([4.0, 4.0], 4.0 / 2.0) self.ctx.rectangle( self.print_bleed_pt + (self.margin_inside_pt if (self.index_page_num + self.page_offset) % 2 else self.margin_outside_pt) + (city_index % 4) * column_width, margin_top_page, column_width, max_drawing_height) self.ctx.stroke() self.ctx.restore() offset_y = margin_top_page # each city/category starts on the corresponding margin for category in self.index_categories[city]: if (offset_y + header_fheight + label_fheight + margin / 2. > (max_drawing_height + margin_top_page)): offset_y = margin_top_page offset_x += delta_x actual_n_cols += 1 if actual_n_cols == columns_count: self._new_page() self._draw_page_stroke() self._draw_page_content_stroke() actual_n_cols = 0 city_header_height = 0 # no city-header on the additional city-pages margin_top_page = margin_top offset_y = margin_top_page offset_x = orig_offset_x delta_x = orig_delta_x max_drawing_height = needed_drawing_height - max_drawing_height + margin_top_page # OR index_area_h_pt => its a new page, full index_area_h_pt is available now # LOG.debug( "NEW PAGE (before category %s). actual_n_cols == columns_count (%d). needed_drawing_height %d, max_drawing_height %d" % (category.name, columns_count, needed_drawing_height, max_drawing_height)) if Renderer.DEBUG: self.ctx.save() self.ctx.set_source_rgba(1, 0, 0, .75) self.ctx.set_dash([8.0, 8.0], 8.0 / 2.0) self.ctx.rectangle( self.rendering_area_x + (self.margin_inside_pt if (self.index_page_num + self.page_offset) % 2 else self.margin_outside_pt) + offset_x, offset_y, column_width, max_drawing_height) self.ctx.stroke() self.ctx.restore() category_height = category.label_drawing_height(header_layout) #LOG.debug("category %s, height draw %d | %d | %d | %d" % (category.name, category_height, header_fascent, UTILS.convert_pt_to_dots(header_fascent, dpi), header_fheight)) category.draw( self._i18n.isrtl(), self.ctx, pc, header_layout, UTILS.convert_pt_to_dots(header_fascent, dpi), UTILS.convert_pt_to_dots(header_fheight, dpi), UTILS.convert_pt_to_dots( self.rendering_area_x + (self.margin_inside_pt if (self.index_page_num + self.page_offset) % 2 else self.margin_outside_pt) + offset_x, dpi), UTILS.convert_pt_to_dots( self.rendering_area_y + offset_y + header_fascent, dpi)) offset_y += category_height for street in category.items: label_height = street.label_drawing_height(label_layout) if (offset_y + label_height + margin / 2. > (max_drawing_height + margin_top_page)): offset_y = margin_top_page offset_x += delta_x actual_n_cols += 1 if actual_n_cols == columns_count: LOG.debug( "NEW PAGE (before street %s). actual_n_cols %d == columns_count %d" % (street.label, actual_n_cols, columns_count)) self._new_page() self._draw_page_stroke() self._draw_page_content_stroke() actual_n_cols = 0 city_header_height = 0 margin_top_page = margin_top offset_y = margin_top_page offset_x = orig_offset_x delta_x = orig_delta_x max_drawing_height = needed_drawing_height - max_drawing_height + margin_top_page if Renderer.DEBUG: self.ctx.save() self.ctx.set_source_rgba(1, 0, 0, .75) self.ctx.set_dash([8.0, 8.0], 8.0 / 2.0) self.ctx.rectangle( self.rendering_area_x + (self.margin_inside_pt if (self.index_page_num + self.page_offset) % 2 else self.margin_outside_pt) + offset_x, offset_y, column_width, max_drawing_height) self.ctx.stroke() self.ctx.restore() self.ctx.set_source_rgb(0, 0, 0) if (street.color is None): self.ctx.set_source_rgb(0, 0, 0) else: color = tuple( int(street.color.lstrip('#')[i:i + 2], 16) / 255. for i in (0, 2, 4)) # draw colorized rectangle next to street self.ctx.save() self.ctx.set_source_rgb(color[0], color[1], color[2]) self.ctx.rectangle( self.rendering_area_x + (self.margin_inside_pt if (self.index_page_num + self.page_offset) % 2 else self.margin_outside_pt) + offset_x, self.rendering_area_y + offset_y, 5, label_height) self.ctx.fill() self.ctx.restore() # alternative: colorize street-text: # self.ctx.set_source_rgb(color[0],color[1],color[2]) street.draw( self._i18n.isrtl(), self.ctx, pc, column_layout, UTILS.convert_pt_to_dots(label_fascent, dpi), UTILS.convert_pt_to_dots(label_fheight, dpi), UTILS.convert_pt_to_dots( self.rendering_area_x + 5 + (self.margin_inside_pt if (self.index_page_num + self.page_offset) % 2 else self.margin_outside_pt) + offset_x, dpi), UTILS.convert_pt_to_dots( self.rendering_area_y + offset_y + label_fascent, dpi), label_layout, UTILS.convert_pt_to_dots(label_height, dpi), UTILS.convert_pt_to_dots(max_location_drawing_width, dpi)) offset_y += label_height self.ctx.restore()