kopia lustrzana https://github.com/hholzgra/ocitysmap
				
				
				
			Determine the font size to use to render index in area.
With this patch, it is now possible to specify different header/label styles when contructing StreetIndexRenederer objects. This will allow the renderer to use the first header/label font from this list that allows to render the index into the specified area. For now the list of possible header/label fonts is hard-coded as the default parameter of the constructor.stable
							rodzic
							
								
									362a0f8812
								
							
						
					
					
						commit
						bdcc6b01f0
					
				|  | @ -85,17 +85,18 @@ if __name__ == '__main__': | |||
|         ctx.stroke() | ||||
| 
 | ||||
|         # Precompute index area | ||||
|         x,y,w,h,ncols = index.precompute_occupation_area(surface, x,y,w,h, | ||||
|                                                          freedom_dimension, | ||||
|                                                          alignment) | ||||
|         rendering_area = index.precompute_occupation_area(surface, x,y,w,h, | ||||
|                                                           freedom_dimension, | ||||
|                                                           alignment) | ||||
| 
 | ||||
|         # Draw a green background for the precomputed area | ||||
|         ctx.set_source_rgba(0,1,0,.5) | ||||
|         ctx.rectangle(x,y,w,h) | ||||
|         ctx.rectangle(rendering_area.x, rendering_area.y, | ||||
|                       rendering_area.w, rendering_area.h) | ||||
|         ctx.fill() | ||||
| 
 | ||||
|         # Render the index | ||||
|         index.render(surface,x,y,w,h,ncols) | ||||
|         index.render(surface, rendering_area) | ||||
| 
 | ||||
|     _render('height', 'top') | ||||
|     surface.show_page() | ||||
|  |  | |||
|  | @ -32,6 +32,68 @@ import commons | |||
| 
 | ||||
| l = logging.getLogger('ocitysmap') | ||||
| 
 | ||||
| 
 | ||||
| class StreetIndexRenderingStyle: | ||||
|     """ | ||||
|     The StreetIndexRenderingStyle class defines how the header and | ||||
|     label items should be drawn (font family, size, etc.). | ||||
|     """ | ||||
|     __slots__ = ["header_font_spec", "label_font_spec"] | ||||
|     header_font_spec = None | ||||
|     label_font_spec  = None | ||||
| 
 | ||||
|     def __init__(self, header_font_spec, label_font_spec): | ||||
|         """ | ||||
|         Specify how the headers and label should be rendered. The | ||||
|         Pango Font Speficication strings below are of the form | ||||
|         "serif,monospace bold italic condensed 16". See | ||||
|         http://www.pygtk.org/docs/pygtk/class-pangofontdescription.html | ||||
|         for more details. | ||||
| 
 | ||||
|         Args: | ||||
|            header_font_spec (str): Pango Font Specification for the headers. | ||||
|            label_font_spec (str): Pango Font Specification for the labels. | ||||
|         """ | ||||
|         self.header_font_spec = header_font_spec | ||||
|         self.label_font_spec  = label_font_spec | ||||
| 
 | ||||
|     def __str__(self): | ||||
|         return "Style(headers=%s, labels=%s)" % (repr(self.header_font_spec), | ||||
|                                                  repr(self.label_font_spec)) | ||||
| 
 | ||||
| 
 | ||||
| class StreetIndexRenderingArea: | ||||
|     """ | ||||
|     The StreetIndexRenderingArea class describes the parameters of the | ||||
|     Cairo area and its parameters (fonts) where the index should be | ||||
|     renedered. It is basically returned by | ||||
|     StreetIndexRenderer::precompute_occupation_area() and used by | ||||
|     StreetIndexRenderer::render(). All its attributes x,y,w,h may be | ||||
|     used by the global map rendering engines. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, street_index_rendering_style, x, y, w, h, n_cols): | ||||
|         """ | ||||
|         Describes the Cairo area to use when rendering the index. | ||||
| 
 | ||||
|         Args: | ||||
|              street_index_rendering_style (StreetIndexRenderingStyle): | ||||
|                    how to render the text inside the index | ||||
|              x (int): horizontal origin position (cairo units). | ||||
|              y (int): vertical origin position (cairo units). | ||||
|              w (int): width of area to use (cairo units). | ||||
|              h (int): height of area to use (cairo units). | ||||
|              n_cols (int): number of columns in the index. | ||||
|         """ | ||||
|         self.rendering_style = street_index_rendering_style | ||||
|         self.x, self.y, self.w, self.h, self.n_cols = x, y, w, h, n_cols | ||||
| 
 | ||||
|     def __str__(self): | ||||
|         return "Area(%s, %dx%d+%d+%d, n_cols=%d)" \ | ||||
|             % (self.rendering_style, | ||||
|                self.w, self.h, self.x, self.y, self.n_cols) | ||||
| 
 | ||||
| 
 | ||||
| class StreetIndexRenderer: | ||||
|     """ | ||||
|     The StreetIndex class encapsulate all the logic related to the querying and | ||||
|  | @ -39,13 +101,32 @@ class StreetIndexRenderer: | |||
|     """ | ||||
| 
 | ||||
|     def __init__(self, i18n, index_categories, | ||||
|                  header_font_spec = 'Georgia Bold', | ||||
|                  label_font_spec = 'DejaVu'): | ||||
|         self._i18n = i18n | ||||
|                  street_index_rendering_styles \ | ||||
|                      = [ StreetIndexRenderingStyle('Georgia Bold 16', | ||||
|                                                    'DejaVu 12'), | ||||
|                          StreetIndexRenderingStyle('Georgia Bold 14', | ||||
|                                                    'DejaVu 10'), | ||||
|                          StreetIndexRenderingStyle('Georgia Bold 12', | ||||
|                                                    'DejaVu 8'), | ||||
|                          StreetIndexRenderingStyle('Georgia Bold 10', | ||||
|                                                    'DejaVu 7'), | ||||
|                          StreetIndexRenderingStyle('Georgia Bold 8', | ||||
|                                                    'DejaVu 6'), | ||||
|                          StreetIndexRenderingStyle('Georgia Bold 6', | ||||
|                                                    'DejaVu 5'), | ||||
|                          StreetIndexRenderingStyle('Georgia Bold 5', | ||||
|                                                    'DejaVu 4'), | ||||
|                          StreetIndexRenderingStyle('Georgia Bold 4', | ||||
|                                                    'DejaVu 3'), | ||||
|                          StreetIndexRenderingStyle('Georgia Bold 3', | ||||
|                                                    'DejaVu 2'), | ||||
|                          StreetIndexRenderingStyle('Georgia Bold 2', | ||||
|                                                    'DejaVu 2'), | ||||
|                          StreetIndexRenderingStyle('Georgia Bold 1', | ||||
|                                                    'DejaVu 1'), ] ): | ||||
|         self._i18n             = i18n | ||||
|         self._index_categories = index_categories | ||||
| 
 | ||||
|         self._header_fd = pango.FontDescription(header_font_spec) | ||||
|         self._label_fd = pango.FontDescription(label_font_spec) | ||||
|         self._rendering_styles = street_index_rendering_styles | ||||
| 
 | ||||
|     def precompute_occupation_area(self, surface, x, y, w, h, | ||||
|                                    freedom_direction, alignment): | ||||
|  | @ -66,10 +147,10 @@ class StreetIndexRenderer: | |||
|                 of 'height', 'left' or 'right' for 'width'. Tells which side to | ||||
|                 stick the index to. | ||||
| 
 | ||||
|         Returns the recommended actual graphical bounding box (new_x, | ||||
|         new_y, new_w, new_h, n_cols) where the index should be | ||||
|         rendered. Raise IndexDoesNotFitError when the provided area's | ||||
|         surface is not enough to hold the index. | ||||
|         Returns the actual graphical StreetIndexRenderingArea defining | ||||
|         how and where the index should be rendered. Raise | ||||
|         IndexDoesNotFitError when the provided area's surface is not | ||||
|         enough to hold the index. | ||||
|         """ | ||||
|         if ((freedom_direction == 'height' and | ||||
|              alignment not in ('top', 'bottom')) or | ||||
|  | @ -80,23 +161,37 @@ class StreetIndexRenderer: | |||
|         if not self._index_categories: | ||||
|             raise commons.IndexEmptyError | ||||
| 
 | ||||
|         ctx = cairo.Context(surface) | ||||
|         l.debug("Determining inde area within %dx%d+%d+%d aligned %s/%s..." | ||||
|                 % (w,h,x,y, alignment, freedom_direction)) | ||||
| 
 | ||||
|         # Create a PangoCairo context for drawing to Cairo | ||||
|         pc = pangocairo.CairoContext(ctx) | ||||
|         ctx = cairo.Context(surface) | ||||
|         pc  = pangocairo.CairoContext(ctx) | ||||
| 
 | ||||
|         n_cols, min_dimension = self._compute_columns_split(pc, | ||||
|                                                             w, h, 12, 16, | ||||
|                                                             freedom_direction) | ||||
|         # Iterate over the rendering_styles until we find a suitable layout | ||||
|         rendering_style = None | ||||
|         for rs in self._rendering_styles: | ||||
|             l.debug("Trying index fit using %s..." % rs) | ||||
|             try: | ||||
|                 n_cols, min_dimension \ | ||||
|                     = self._compute_columns_split(pc, rs, w, h, | ||||
|                                                   freedom_direction) | ||||
| 
 | ||||
|         self._label_fd.set_size(12 * pango.SCALE) | ||||
|         self._header_fd.set_size(16 * pango.SCALE) | ||||
|                 # Great: index did fit OK ! | ||||
|                 rendering_style = rs | ||||
|                 break | ||||
| 
 | ||||
|         label_layout, label_fascent, label_fheight, label_em = \ | ||||
|                 self._create_layout_with_font(pc, self._label_fd) | ||||
|         header_layout, header_fascent, header_fheight, header_em = \ | ||||
|                 self._create_layout_with_font(pc, self._header_fd) | ||||
|             except commons.IndexDoesNotFitError: | ||||
|                 # Index did not fit => try smaller... | ||||
|                 l.debug("Index %s too large: should try a smaller one." | ||||
|                         % rs) | ||||
|                 continue | ||||
| 
 | ||||
|         # Index really did not fit with any of the rendering styles ? | ||||
|         if not rendering_style: | ||||
|             raise commons.IndexDoesNotFitError("Index does not fit in area") | ||||
| 
 | ||||
|         # Realign at bottom/top left/right | ||||
|         if freedom_direction == 'height': | ||||
|             index_width  = w | ||||
|             index_height = min_dimension | ||||
|  | @ -111,42 +206,45 @@ class StreetIndexRenderer: | |||
|         if alignment == 'right': | ||||
|             base_offset_x = w - index_width | ||||
| 
 | ||||
|         return (x+base_offset_x, y+base_offset_y, | ||||
|                 index_width, index_height, n_cols) | ||||
|         area = StreetIndexRenderingArea(rendering_style, | ||||
|                                         x+base_offset_x, y+base_offset_y, | ||||
|                                         index_width, index_height, n_cols) | ||||
|         l.debug("Will be able to render index in %s" % area) | ||||
|         return area | ||||
| 
 | ||||
| 
 | ||||
|     def render(self, surface, x, y, w, h, n_cols): | ||||
|     def render(self, surface, rendering_area): | ||||
|         """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: | ||||
|             surface (cairo.Surface): the cairo surface to render into. | ||||
|             x (int): horizontal origin position, in pixels. | ||||
|             y (int): vertical origin position, in pixels. | ||||
|             w (int): maximum usable width for the index, in dots (Cairo unit). | ||||
|             h (int): maximum usable height for the index, in dots (Cairo unit). | ||||
|             rendering_area (StreetIndexRenderingArea): the result from | ||||
|                 precompute_occupation_area(). | ||||
|         """ | ||||
| 
 | ||||
|         if not self._index_categories: | ||||
|             raise commons.IndexEmptyError | ||||
| 
 | ||||
|         ctx = cairo.Context(surface) | ||||
|         ctx.move_to(x, y) | ||||
|         ctx.move_to(rendering_area.x, rendering_area.y) | ||||
| 
 | ||||
|         # Create a PangoCairo context for drawing to Cairo | ||||
|         pc = pangocairo.CairoContext(ctx) | ||||
| 
 | ||||
|         self._label_fd.set_size(12 * pango.SCALE) | ||||
|         self._header_fd.set_size(16 * pango.SCALE) | ||||
|         header_fd = pango.FontDescription(rendering_area.rendering_style.header_font_spec) | ||||
|         label_fd  = pango.FontDescription(rendering_area.rendering_style.label_font_spec) | ||||
| 
 | ||||
|         label_layout, label_fascent, label_fheight, label_em = \ | ||||
|                 self._create_layout_with_font(pc, self._label_fd) | ||||
|                 self._create_layout_with_font(pc, label_fd) | ||||
|         header_layout, header_fascent, header_fheight, header_em = \ | ||||
|                 self._create_layout_with_font(pc, self._header_fd) | ||||
|                 self._create_layout_with_font(pc, header_fd) | ||||
| 
 | ||||
|         cairo_colspace = label_em | ||||
|         column_width = int(math.floor((w + cairo_colspace) / n_cols)) | ||||
|         column_width = int(math.floor(float(rendering_area.w | ||||
|                                             + cairo_colspace) | ||||
|                                       / rendering_area.n_cols)) | ||||
| 
 | ||||
|         label_layout.set_width((column_width - label_em) * pango.SCALE) | ||||
|         header_layout.set_width((column_width - label_em) * pango.SCALE) | ||||
|  | @ -156,30 +254,30 @@ class StreetIndexRenderer: | |||
|             offset_x = 0 | ||||
|         else: | ||||
|             delta_x  = - column_width | ||||
|             offset_x = w - column_width + cairo_colspace | ||||
|             offset_x = rendering_area.w - column_width + cairo_colspace | ||||
| 
 | ||||
|         offset_y = 0 | ||||
|         for category in self._index_categories: | ||||
|             if offset_y + header_fheight + label_fheight > h: | ||||
|             if offset_y + header_fheight + label_fheight > rendering_area.h: | ||||
|                 offset_y = 0 | ||||
|                 offset_x += delta_x | ||||
| 
 | ||||
|             category.draw(self._i18n.isrtl(), ctx, pc, header_layout, | ||||
|                     header_fascent, header_fheight, | ||||
|                     x + offset_x, | ||||
|                     y + offset_y + header_fascent) | ||||
|                           header_fascent, header_fheight, | ||||
|                           rendering_area.x + offset_x, | ||||
|                           rendering_area.y + offset_y + header_fascent) | ||||
| 
 | ||||
|             offset_y += header_fheight | ||||
| 
 | ||||
|             for street in category.items: | ||||
|                 if offset_y + label_fheight > h: | ||||
|                 if offset_y + label_fheight > rendering_area.h: | ||||
|                     offset_y = 0 | ||||
|                     offset_x += delta_x | ||||
| 
 | ||||
|                 street.draw(self._i18n.isrtl(), ctx, pc, label_layout, | ||||
|                         label_fascent, label_fheight, | ||||
|                         x + offset_x, | ||||
|                         y + offset_y + label_fascent) | ||||
|                             label_fascent, label_fheight, | ||||
|                             rendering_area.x + offset_x, | ||||
|                             rendering_area.y + offset_y + label_fascent) | ||||
| 
 | ||||
|                 offset_y += label_fheight | ||||
| 
 | ||||
|  | @ -197,6 +295,7 @@ class StreetIndexRenderer: | |||
| 
 | ||||
|         return layout, fascent, fheight, em | ||||
| 
 | ||||
| 
 | ||||
|     def _compute_lines_occupation(self, pc, font_desc, n_em_padding, | ||||
|                                   text_lines): | ||||
|         """Compute the visual dimension parameters of the initial long column | ||||
|  | @ -217,7 +316,8 @@ class StreetIndexRenderer: | |||
|             fheight: scaled font height. | ||||
|         """ | ||||
| 
 | ||||
|         layout, fascent, fheight, em = self._create_layout_with_font(pc, font_desc) | ||||
|         layout, fascent, fheight, em = self._create_layout_with_font(pc, | ||||
|                                                                      font_desc) | ||||
|         width = max(map(lambda x: self._label_width(layout, x), text_lines)) | ||||
|         # Save some extra space horizontally | ||||
|         width += n_em_padding * em | ||||
|  | @ -227,31 +327,32 @@ class StreetIndexRenderer: | |||
|         return {'column_width': width, 'column_height': height, | ||||
|                 'fascent': fascent, 'fheight': fheight} | ||||
| 
 | ||||
| 
 | ||||
|     def _label_width(self, layout, label): | ||||
|         layout.set_text(label) | ||||
|         return layout.get_size()[0] / pango.SCALE | ||||
| 
 | ||||
|     def _compute_column_occupation(self, pc, label_font_size, | ||||
|                                    header_font_size): | ||||
| 
 | ||||
|     def _compute_column_occupation(self, pc, rendering_style): | ||||
|         """Returns the size of the tall column with all headers, labels and | ||||
|         squares for the given font sizes. | ||||
| 
 | ||||
|         Args: | ||||
|             pc (pangocairo.CairoContext): the PangoCairo context. | ||||
|             label_font_size (int): font size for street labels and squares. | ||||
|             header_font_size (int): font size for headers. | ||||
|             rendering_style (StreetIndexRenderingStyle): how to render the | ||||
|                 headers and labels. | ||||
|         """ | ||||
| 
 | ||||
|         self._label_fd.set_size(label_font_size * pango.SCALE) | ||||
|         self._header_fd.set_size(header_font_size * pango.SCALE) | ||||
|         header_fd = pango.FontDescription(rendering_style.header_font_spec) | ||||
|         label_fd  = pango.FontDescription(rendering_style.label_font_spec) | ||||
| 
 | ||||
|         # Account for maximum square width (at worst "Z99-Z99") | ||||
|         label_block = self._compute_lines_occupation(pc, self._label_fd, 1+7, | ||||
|         # Account for maximum square width (at worst " " + "Z99-Z99") | ||||
|         label_block = self._compute_lines_occupation(pc, label_fd, 1+7, | ||||
|                 reduce(lambda x,y: x+y.get_all_item_labels(), | ||||
|                        self._index_categories, [])) | ||||
| 
 | ||||
|         # Reserve a small margin around the category headers | ||||
|         headers_block = self._compute_lines_occupation(pc, self._header_fd, 2, | ||||
|         headers_block = self._compute_lines_occupation(pc, header_fd, 2, | ||||
|                 [x.name for x in self._index_categories]) | ||||
| 
 | ||||
|         column_width = max(label_block['column_width'], | ||||
|  | @ -262,8 +363,9 @@ class StreetIndexRenderer: | |||
|         return column_width, column_height, \ | ||||
|                 max(label_block['fheight'], headers_block['fheight']) | ||||
| 
 | ||||
|     def _compute_columns_split(self, pc, zone_width_dots, zone_height_dots, | ||||
|                                label_font_size, header_font_size, | ||||
| 
 | ||||
|     def _compute_columns_split(self, pc, rendering_style, | ||||
|                                zone_width_dots, zone_height_dots, | ||||
|                                freedom_direction): | ||||
|         """Computes the columns split for this index. From the one tall column | ||||
|         width and height it finds the number of columns fitting on the zone | ||||
|  | @ -274,12 +376,12 @@ class StreetIndexRenderer: | |||
| 
 | ||||
|         Args: | ||||
|             pc (pangocairo.CairoContext): the PangoCairo context. | ||||
|             rendering_style (StreetIndexRenderingStyle): how to render the | ||||
|                 headers and labels. | ||||
|             zone_width_dots (float): maximum width of the Cairo zone dedicated | ||||
|                 to the index. | ||||
|             zone_height_dots (float): maximum height of the Cairo zone | ||||
|                 dedicated to the index. | ||||
|             label_font_size (int): font size for street labels and squares. | ||||
|             header_font_size (int): font size for headers. | ||||
|             freedom_direction (string): the zone dimension that is flexible for | ||||
|                 rendering this index, can be 'width' or 'height'. If the | ||||
|                 streets don't fill the zone dedicated to the index, we need to | ||||
|  | @ -290,8 +392,7 @@ class StreetIndexRenderer: | |||
|         """ | ||||
| 
 | ||||
|         tall_width, tall_height, vertical_extra = \ | ||||
|                 self._compute_column_occupation(pc, label_font_size, | ||||
|                                                 header_font_size) | ||||
|                 self._compute_column_occupation(pc, rendering_style) | ||||
| 
 | ||||
|         if zone_width_dots < tall_width: | ||||
|             raise commons.IndexDoesNotFitError | ||||
|  | @ -328,6 +429,8 @@ if __name__ == '__main__': | |||
| 
 | ||||
|     import render | ||||
| 
 | ||||
|     logging.basicConfig(level=logging.DEBUG) | ||||
| 
 | ||||
|     width = 72*21./2.54 | ||||
|     height = .75 * 72*29.7/2.54 | ||||
| 
 | ||||
|  | @ -348,7 +451,9 @@ if __name__ == '__main__': | |||
|             return self.rtl | ||||
| 
 | ||||
|     streets = [] | ||||
|     for i in ['A', 'B', 'C', 'D', 'E', 'Schools', 'Public buildings']: | ||||
|     for i in ['A', 'B', # 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', | ||||
|               'N', 'O', # 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', | ||||
|               'Schools', 'Public buildings']: | ||||
|         items = [] | ||||
|         for label, location_str in [(rnd_str(10).capitalize(), | ||||
|                                      '%s%d-%s%d' \ | ||||
|  | @ -376,17 +481,18 @@ if __name__ == '__main__': | |||
|         ctx.stroke() | ||||
| 
 | ||||
|         # Precompute index area | ||||
|         x,y,w,h,ncols = index.precompute_occupation_area(surface, x,y,w,h, | ||||
|                                                          freedom_dimension, | ||||
|                                                          alignment) | ||||
|         rendering_area = index.precompute_occupation_area(surface, x,y,w,h, | ||||
|                                                           freedom_dimension, | ||||
|                                                           alignment) | ||||
| 
 | ||||
|         # Draw a green background for the precomputed area | ||||
|         ctx.set_source_rgba(0,1,0,.5) | ||||
|         ctx.rectangle(x,y,w,h) | ||||
|         ctx.rectangle(rendering_area.x, rendering_area.y, | ||||
|                       rendering_area.w, rendering_area.h) | ||||
|         ctx.fill() | ||||
| 
 | ||||
|         # Render the index | ||||
|         index.render(surface,x,y,w,h,ncols) | ||||
|         index.render(surface, rendering_area) | ||||
| 
 | ||||
| 
 | ||||
|     _render('height', 'top') | ||||
|  |  | |||
		Ładowanie…
	
		Reference in New Issue
	
	 David Decotigny
						David Decotigny