James Tocknell avatar James Tocknell committed 86412ce Merge

Merged

Comments (0)

Files changed (10)

docs/handlers.rst

+Handlers
+~~~~~~~~
+Handlers must be an object which specifies a ``layout`` method which takes a
+theme object, which must have a ``templates`` attribute. If none of the required
+templates are given in the ``templates`` attribute, then the handler should
+raise a ``NotImplemented`` exception. Handlers may warn if some templates that
+the handler can use are not given. Handlers should specify what templates can be
+used as a list of strings in ``Handler.templates``. If the handler cannot render
+for any reason, it should raise a ``Handler.CannotRender`` exception.
+
+.. automodule:: shinypress.handler
+    :members:
+
 
 This section describes the interaction between handlers and themes.
 
+.. toctree::
+
+    primitives
+    handlers
+    themes
+
 Usage
 ~~~~~
 
         for template, blocks in slide_layouts:
             theme.render(ctx, template, blocks)
             surf.show_page()
-
-Primitives
-~~~~~~~~~~
-
-There are some simple functions provided for rendering text and images:
-
-.. automodule:: shinypress.render
-    :members:
-
-Handlers
-~~~~~~~~
-Handlers must be an object which specifies a ``layout`` method which takes a
-theme object, which must have a ``templates`` attribute. If none of the required
-templates are given in the ``templates`` attribute, then the handler should
-raise a ``NotImplemented`` exception. Handlers may warn if some templates that
-the handler can use are not given. Handlers should specify what templates can be
-used as a list of strings in ``Handler.templates``. If the handler cannot render
-for any reason, it should raise a ``Handler.CannotRender`` exception.
-
-.. automodule:: shinypress.handler
-    :members:
-
-Themes
-~~~~~~
-
-Themes are responsible for deciding where the handlers should draw their blocks.
-
-.. automodule:: shinypress.theme
-    :members:
-

docs/primitives.rst

+Primitives
+~~~~~~~~~~
+
+.. module:: shinypress.render
+
+Got an image? Let’s render that for you:
+
+.. autofunction:: render_image
+
+Oh, you have some text instead? Well, let’s do that, then:
+
+.. autofunction:: render_text
+.. autoclass:: HAlignment
+.. autoclass:: VAlignment
+
+And there’s also a utility function useful for scaling content, used by those functions above.
+
+.. autofunction:: scale
+.. autoclass:: ScalingMode
+
 
 .. automodule:: shinypress.text.multiple
     :members:
+
+Single slide layout
+~~~~~~~~~~~~~~~~~~~
+
+There is a big function for rendering text, which requires three functions, which:
+
+* create a Pango.layout of the text at a given size
+* calculate the cost of a given Pango.layout
+* generate the possible sizes
+
+.. automodule:: shinypress.text.single
+    :members:
+Themes
+~~~~~~
+
+Themes are responsible for deciding where the handlers should draw their blocks.
+
+.. automodule:: shinypress.theme
+    :members:
+

shinypress/render.py

     """
     pass
 
-def render_text(ctx, text, size, **todo_fill_in_these_kwargs):
+class HAlignment(object):
     """
-    Render some text onto the given Cairo context.
+    Horizontal alignment.
+    Not an enum in the true sense, just some related constants.
+    """
+    LEFT = 0.0
+    CENTER = 0.5
+    RIGHT = 1.0
 
-    :param ctx: a Cairo context
-    :param text: the text to be rendered
-    :type text: unicode
-    :param size: the dimensions of the text box
-    :type size: a (width, height) tuple
+class VAlignment(object):
+    """
+    Vertical alignment.
+    Not an enum in the true sense, just some related constants.
+    """
+    TOP = 0.0
+    MIDDLE = 0.5
+    BOTTOM = 1.0
+
+def render_text(
+    ctx, layout, size,
+    halign=HAlignment.LEFT,
+    valign=VAlignment.MIDDLE,
+    scaling_mode=ScalingMode.NONE,
+):
+    """
+    Render a Pango.layout on a Cairo context.
+
+    The text is first scaled according to the given scaling_mode (see the
+    ScalingMode enum for possible values).
+
+    The ``halign`` parameter determines the horizontal alignment (0.0 is left
+    aligned, 0.5 is in the center, 1.0 is right aligned). Similarly, ``valign``
+    determines vertical alignment (0.0 is top, 0.5 is middle, 1.0 is bottom).
+    There are constant values provided in the HAlignment and VAlignment enum
+    which should be used to improve readability.
+
+    :param ctx: the Cairo context to draw on
+    :type ctx: cairo.Context
+    :param layout: the text layout to draw
+    :type layout: Pango.Layout
+    :param halign: how the layout should be horizontally aligned
+    :type halign: float or HAlignment
+    :param valign: how the layout should be vertically aligned
+    :type valign: float or VAlignment
+    :param scaling_mode: how to resize the text to fit the given box
+    :type scaling_mode: shinypress.render.ScalingMode
     """
     pass

shinypress/text/single.py

+from functools import partial
+
+from gi.repository import Pango
+from gi.repository import PangoCairo
+
+def get_box_cost(actual_size, target_size):
+    aw, ah = actual_size
+    tw, th = target_size
+    if aw > tw or ah > th:
+        return float('inf')
+    height_penalty = 1 - (float(ah) / th) ** 4
+    width_penalty = 1 - (float(aw) / tw) ** 2
+    return (width_penalty + height_penalty) / 2.0
+
+def layout_text(ctx, text, target_size, size):
+    font = Pango.FontDescription()
+    font.set_family('Droid Sans')
+    font.set_size(size * Pango.SCALE)
+
+    layout = PangoCairo.create_layout(ctx)
+    layout.set_font_description(font)
+    layout.set_justify(True)
+    layout.set_text(text, -1)
+    layout.set_width(target_size[0] * Pango.SCALE)
+    layout.set_spacing(size * 0.2 * Pango.SCALE)
+
+    actual_size = layout.get_pixel_size()
+    cost = get_box_cost(actual_size, target_size)
+
+    return cost, layout
+
+def generate_sizes(min_size, max_size, scale):
+    if not 0 < scale < 1:
+        scale = 1.0 / scale
+    assert 0 < scale < 1
+
+    sizes = []
+    # generate the minor scale
+    size = min_size
+    while size < max_size:
+        sizes.append(size)
+        size /= scale
+    # generate the major scale
+    size = max_size
+    while size > min_size:
+        sizes.append(size)
+        size *= scale
+
+    return sorted(set(sizes))
+
+def minimise(sizes, fn):
+    """Find the maximum size with the lowest cost."""
+    best_cost = float('inf')
+    best = None
+
+    while len(sizes) > 1:
+        # find the middle element
+        i = len(sizes) // 2
+        size = sizes[i]
+        cost, layout = fn(size)
+
+        # if this beat our current best, we should only try sizes larger than
+        # this one.
+        if cost <= best_cost:
+            best_cost = cost
+            best = (size, layout)
+            sizes = sizes[i + 1:]
+        # otherwise, this sizes and anything larger is bad
+        else:
+            sizes = sizes[:i]
+
+    return best_cost, best
+
+def render_text(
+    ctx, text, size,
+    min_font_size, max_font_size, factor,
+):
+    """
+    Render text on a Cairo context.
+
+    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
+    :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
+    :type factor: float
+    """
+    fn = partial(layout_text, ctx, text, size)
+    sizes = generate_sizes(min_font_size, max_font_size, factor)
+
+    cost, (size, layout) = minimise(sizes, fn)
+    PangoCairo.show_layout(ctx, layout)

slide-breaking/main.py

-from functools import partial
+import cairo
 
-import cairo
-from gi.repository import PangoCairo
-
-from text.single import generate_sizes, layout_text
-from utils import minimise
+from text.single import render_text
 
 TEXT = open('sample.txt', 'rU').read()
 
 surf = cairo.PDFSurface('blah.pdf', WIDTH, HEIGHT)
 ctx = cairo.Context(surf)
 
-fn = partial(layout_text, ctx, TEXT, (WIDTH, HEIGHT))
-sizes = generate_sizes(18, 48, 1.25)
-
-score, (size, layout) = minimise(sizes, fn)
-PangoCairo.show_layout(ctx, layout)
+render_text(ctx, TEXT, (WIDTH, HEIGHT), 18, 48, 1.25)
 surf.show_page()

slide-breaking/text/single.py

-from gi.repository import Pango
-from gi.repository import PangoCairo
-
-def score_box(actual_size, target_size):
-    aw, ah = actual_size
-    tw, th = target_size
-    if aw > tw or ah > th:
-        return float('inf')
-    height_penalty = 1 - (float(ah) / th) ** 4
-    width_penalty = 1 - (float(aw) / tw) ** 2
-    return (width_penalty + height_penalty) / 2.0
-
-def layout_text(ctx, text, target_size, size):
-    font = Pango.FontDescription()
-    font.set_family('Droid Sans')
-    font.set_size(size * Pango.SCALE)
-
-    layout = PangoCairo.create_layout(ctx)
-    layout.set_font_description(font)
-    layout.set_justify(True)
-    layout.set_text(text, -1)
-    layout.set_width(target_size[0] * Pango.SCALE)
-    layout.set_spacing(size * 0.2 * Pango.SCALE)
-
-    actual_size = layout.get_pixel_size()
-    score = score_box(actual_size, target_size)
-
-    return score, layout
-
-def generate_sizes(min_size, max_size, scale):
-    if not 0 < scale < 1:
-        scale = 1.0 / scale
-    assert 0 < scale < 1
-
-    sizes = []
-    # generate the minor scale
-    size = min_size
-    while size < max_size:
-        sizes.append(size)
-        size /= scale
-    # generate the major scale
-    size = max_size
-    while size > min_size:
-        sizes.append(size)
-        size *= scale
-
-    return sorted(set(sizes))

slide-breaking/utils.py

-def minimise(sizes, fn):
-    """Find the maximum size with the lowest score."""
-    best_score = float('inf')
-    best = None
-
-    while len(sizes) > 1:
-        # find the middle element
-        i = len(sizes) // 2
-        size = sizes[i]
-        score, layout = fn(size)
-
-        # if this beat our current best, we should only try sizes larger than
-        # this one.
-        if score <= best_score:
-            best_score = score
-            best = (size, layout)
-            sizes = sizes[i + 1:]
-        # otherwise, this sizes and anything larger is bad
-        else:
-            sizes = sizes[:i]
-
-    return best_score, best
-
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.