Peter Ward avatar Peter Ward committed f551a56

more api improvements for shinypress.text.single

Comments (0)

Files changed (2)

 Single slide layout
 ~~~~~~~~~~~~~~~~~~~
 
-There is a big function for rendering text, which requires three functions, which:
+This module provides functions to find the optimal size for text on a single
+slide.
 
-* create a Pango.layout of the text at a given size
-* calculate the cost of a given Pango.layout
-* generate the possible sizes
+Usage
+-----
+
+.. code-block:: python
+
+    from shinypress.render import layout_text
+    from shinypress.text.single import find_best_size
+
+    ctx = cairo.Context(...)
+
+    text = 'Hello World!'
+    max_width = 600
+    font_family = 'Droid Sans'
+
+    def render_at_size(font_size):
+        return layout_text(
+            ctx, text, max_width,
+            font_family, font_size,
+        )
+
+    layout = find_best_size()
+
+API
+---
 
 .. automodule:: shinypress.text.single
     :members:
+

shinypress/text/single.py

-from gi.repository import PangoCairo
-
-from shinypress.render import layout_text
-
-def get_layout_cost(layout, target_size):
+def bounding_box_cost(layout, target_size, width_factor=2, height_factor=4):
     aw, ah = layout.get_pixel_size()
     tw, th = target_size
     if aw > tw or ah > th:
     width_penalty = 1 - (float(aw) / tw) ** 2
     return (width_penalty + height_penalty) / 2.0
 
-def generate_sizes(min_size, max_size, scale):
-    if not 0 < scale < 1:
-        scale = 1.0 / scale
-    assert 0 < scale < 1
+def generate_size_scale(min_size, max_size, factor):
+    """
+    This method takes a minimum and maximum font size, and scales both by
+    ``factor`` in order to determine the range of font sizes that should be
+    tried.
+    """
+    if not 0 < factor < 1:
+        factor = 1.0 / factor
+    assert 0 < factor < 1
 
     sizes = []
     # generate the minor scale
     size = min_size
     while size < max_size:
         sizes.append(size)
-        size /= scale
+        size /= factor
     # generate the major scale
     size = max_size
     while size > min_size:
         sizes.append(size)
-        size *= scale
+        size *= factor
 
     return sorted(set(sizes))
 
-def minimise(sizes, fn):
-    """Find the maximum size with the lowest cost."""
+def binary_search_local_minimum(sizes, fn):
+    """
+    Binary search a list of items using a cost function to find the best size.
+    The algorithm used will only find a local minimum, so you should only use
+    cost functions where the global minimum is the only local minimum.
+
+    :param sizes: the font sizes to search on
+    :type sizes: list of floats
+    :param fn: the cost function
+    :type fn: lambda size: (cost, layout)
+    :rtype: (cost, (size, layout))
+    """
     best_cost = float('inf')
     best = None
 
 
     return best_cost, best
 
-def render_best_size():
-    pass
-
-def render_text(
-    ctx, text, size,
+def find_best_size_on_scale(
+    ctx,
+    layout_text,
+    size,
     min_font_size, max_font_size, factor,
 ):
     """
-    Render text on a Cairo context.
+    Given a function for rendering text at a given size, a box size, minimum and
+    maximum font sizes and a scaling factor
+    (as per the arguments to :func:`generate_size_scale`),
+    return the best size (and a layout for that size) to fit a given box size.
 
-    This method takes a minimum and maximum font size, and scales both by
-    ``factor`` in order to determine the range of font sizes that should be
-    tried.
-
-    The font size which produces the lowest cost according to layout_text will
-    be used.
-
-    :param ctx: the Cairo context to draw on
-    :param text: the text to draw
-    :type text: unicode
-    :param size: the dimensions of the text box
+    :param layout_text: function which returns a text layout for a given size
+        (consider using :func:`shinypress.render.layout_text`)
+    :type layout_text: lambda float: Pango.Layout
+    :param size: target size for the text to fit into
     :type size: a (width, height) tuple
-    :param min_font_size: minimum font size, in points
-    :param max_font_size: maximum font size, in points
-    :param factor: amount to scale by
+    :param min_font_size: minimum font size
+    :type min_font_size: float
+    :param max_font_size: maximum font size
+    :type max_font_size: float
+    :param factor: scaling factor
     :type factor: float
+    :rtype: (size, Pango.Layout)
     """
     def add_score(font_size):
-        layout = layout_text(
-            ctx, text, size[0],
-            'Droid Sans', font_size,
-        )
-        score = get_layout_cost(layout, size)
+        layout = layout_text(font_size)
+        score = bounding_box_cost(layout, size)
         return score, layout
 
-    sizes = generate_sizes(min_font_size, max_font_size, factor)
-
-    cost, (size, layout) = minimise(sizes, add_score)
-    PangoCairo.show_layout(ctx, layout)
+    sizes = generate_size_scale(min_font_size, max_font_size, factor)
+    cost, (size, layout) = binary_search_local_minimum(sizes, add_score)
+    return size, layout
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.