Development guide for Crosswords
  • Contributing
  • Codebase
  • Designs
    • Crosswords Editor
    • Crosswords Player
    • Puzzle sets
    • Shared code
      • Background shapes for cells
      • ClueGrid widget
      • Crossword Quirks
      • CSS in Crosswords
      • Grid layout
      • GridState Modes refactor, take 2
      • Immutable XwordState
      • Revamping the libipuz edit APIs
      • Overlays on the grid
      • Play Cell
      • Play Grid Sizing
      • The PuzzleStack
      • Word List
      • Print Layout Templates
        • Goals
        • Rationale
        • Overall Approach
        • Areas For Improvement
      • Print Layout
    • Drafts
    • Deprecated
  • Packaging
  • Maintainers
Development guide for Crosswords
  • Designs
  • Shared code
  • Print Layout Templates
  • View page source

Print Layout Templates

Status: Implemented

Author: Toluwaleke Ogundipe

Reviewers: Federico Mena Quintero, Jonathan Blandford

Goals

  • Specify a format for defining reusable print layout templates.

Rationale

Different puzzle kinds require slightly different layouts when printed on paper. All of them require the puzzle grid and the clues, but for example, in arrowword puzzles, the clues are embedded in the grid and they do not require a list of clues.

Overall Approach

Print layout templates are reusable, declarative specifications that define how different elements of a puzzle are arranged and rendered on a printed page.

At its core, a template describes a layout applicable to a set of puzzle kind(s), breaking down the printable area into a hierarchy of elements, each with a distinct role, such as displaying the grid, clues, title, metadata, or other visual components. Thanks to the flexibility of the layout engine, the same template may be used for multiple page sizes.

These templates provide a consistent and flexible way to support various puzzle kinds and page sizes.

The Top-Level

At the top-level, a TEMPLATE contains some metadata, the main page, and an optional overflow page with space for additional clues. Each page is typically a nested set of ELEMENTS.

Templates are defined in the JSON format as follows.

Important

  • The code blocks herein are not valid JSON but descriptions of the expected format.

  • Field/Member names are case-sensitive.

  • Unknown/Unexpected fields are ignored.

{
  // Applicable puzzle kinds (case-insensitive).
  "puzzle_kinds": [ String, ... ],

  // The main page.
  "main_page": Box,

  // Optional: An overflow page for additional clues.
  "overflow": Box
}

Where Box is a container of elements and has the following format:

  {
    // The orientation of the box (case-insensitive); one of:
    //
    // - horizontal
    // - vertical
    "orientation": String

    // Sub-elements of the box.
    "elements": [ Element, ... ]
  }
  • Element defines a template element.

  • must contain at least one non-DIVIDER element.

Note

When dealing with a BOX:

  • the axis of orientation is called the MAJOR axis.

  • the other axis is called the MINOR axis.

Template Elements

There are two basic categories of elements:

  1. DISPLAY elements: These might contain text, a grid, or something else, or even nothing at all (to insert white space between other elements).

  2. CONTAINER elements: These are used to arrange other elements.

Primarily, each element has a KIND and may include kind-specific data to customize its look/behavior, e.g font size/styles for text, or clue flow information for multi-region clue layouts. Additionally, it may also include data for sizing within a container.

Element Dimension Kinds

Each dimension (width or height) of an element can be one of the following kinds (or be computed in one of the following ways):

  • INTRINSIC: This dimension can be computed from just the element data.

  • DERIVED: Given the other dimension (width), this dimension (height) can be computed from the element data.

    Important

    No element may have a DERIVED width. Otherwise, it could result in a situation where a CONTAINER element has both DERIVED width and height, in which case it would be imposible to compute either.

    Thankfully, no element of puzzle printing naturally has a DERIVED width.

  • WEIGHTED: This dimension cannot be computed from the element data; it is computed by the element’s parent box using the element’s ratio.

Every DISPLAY element kind has a pre-defined dimension kind along each axis, but the dimension kind of a container element on either axis depends on the dimension kinds of the elements it contains. The dimension kinds for each element kind are defined later on.

Element Format

An element has the following format:

{
  // The element kind (case-insensitive).
  "kind": String,

  // Optional: Relative ratio of the element within its parent box;
  // Positive.
  "ratio": Float,

  // Optional: The minimum dimension of the element along its parent box'
  // major axis, in millimeters;
  // Positive.
  "minimum": Float,

  // Optional: The maximum dimension of the element along its parent box'
  // major axis, in millimeters;
  // Positive.
  "maximum": Float,

  // Optional: Whether to omit the element if its minimum dimension is not
  // satisfied.
  "cut_off": Boolean,

  // Optional: Whether the element's dimension along its parent box' major
  // axis may be added to, if there's excess space left in the box.
  "takes_excess": Boolean,

  // Optional: Used for element kinds that require extra info.
  "data": ...
}
  • for ratio:

    • the default is 1.0 i.e when not specified.

    • it applies only if the element has a WEIGHTED dimension along its parent box’ major axis e.g a NOTES element in a horizontal box, and ignored otherwise e.g for a NOTES element in a vertical box.

    • within each box, sub-element ratios are relative i.e the normalized/absolute ratio of each sub-element (WEIGHTED) is (sub-element ratio) / (sum of all sub-element ratios); this is more flexible than absolute ratios, without significant additional cost.

  • for minimum:

    • the default is 0.0 i.e when not specified.

    Note

    • The minimum dimension is not guranteed; the parent box can only allocate as much as is available.

    • The minimum dimension applies if and only if the computed dimension is greater than zero.

  • for maximum:

    • if not specified, the element’s dimension is only limited by:

      • its ratio, if it has a WEIGHTED dimension along its parent box’ major axis, or

      • the space available in its parent box.

    • if specified, it must be greater than or equal to minimum.

  • for cut_off:

    • the default is false i.e when not specified.

    • if true and the dimension allocated to the element is less than minimum, the element is omitted and other elements may take up its space.

  • for takes_excess:

    • the default is false i.e when not specified.

    • it only applies to elements with non-WEIGHTED dimensions along the parent box’ major axis (reason explained below); ignored otherwise.

    • if true and the element’s parent box has space left (after computing the dimensions of all sub-elements), the excess space is distributed amongst all sub-elements of the box which have this field set to true and haven’t been cut off, such that they don’t exceed their maximums.

    Note

    Excess space in a box occurs when it contains sub-elements with WEIGHTED dimensions along its major axis and all those sub-elements either get cut off or limited by their maximums.

    Since all sub-elements with WEIGHTED dimensions are already either cut off or allocated their maximums, only sub-elements with non-WEIGHTED dimensions (if any) may take up the excess space.

    Even if there are sub-elements within a box that have this field set to true, all the excess space may not be taken up if those sub-elements have been cut off or are limited by their maximums.

Element Kinds

The element kinds are as follows. Each doesn’t use the data field except stated otherwise.

  • TITLE: The puzzle’s title.

    • The width is WEIGHTED.

    • The height is DERIVED.

    • The data field has the following format:

      {
        // The font description with which to render text
        "font": String
      }
      
    • font is in the format accepted by pango_font_description_from_string(), with the following specifics:

      • only the font size is required, and must be in points.

      • if the font family is not specified, a default is used.

  • METADATA: The publisher, author, date, etc (as many as are defined).

    • The dimension kinds are as for the TITLE element kind.

    • The data field is as for the TITLE element kind.

  • INTRO: The puzzle’s intro.

    • The dimension kinds are as for the TITLE element kind.

    • The data field is as for the TITLE element kind.

  • NOTES: The puzzle’s notes.

    • The dimension kinds are as for the TITLE element kind.

    • The data field is as for the TITLE element kind.

  • GRID: The puzzle’s grid.

    • The width is INTRINSIC.

    • The height is DERIVED.

      Note

      This is so that when the allocated width is less than the intrinsic width, just the right amount of height needed can be computed. Unfortunately, the converse can’t be easily/neatly achieved.

  • CLUES: The puzzle’s clues.

    • The width is WEIGHTED.

    • The height is WEIGHTED.

    • The data field has the following format:

      {
        // A unique (amongst CLUES elements) identifier;
        // Positive.
        "id": Integer,
      
        // Clue direction(s) to contain (case-insensitive). One of:
        //
        // - all: Contain all clue directions, one after another
        // - any: Any available clue direction
        // - <any of the standard IPUZ directions>
        "direction": String,
      
        // The ID of another CLUES element into which the content of this
        // element may flow.
        "flows_into": Integer,
      
        // The font description with which to render text
        "font": String
      }
      
    • id is required.

    • a source is a CLUES element into which no other flows.

    • an extension is a CLUES element into which another flows.

    • all data fields other than id are optional, with the following exception(s):

      • direction and font are required for a source.

    • for direction:

      • if specified, direction is ignored for an extension.

      • if direction all is used, there must be only one source.

      • if direction any is used, the direction of all sources must be any.

      • no two sources may have the same IPUZ direction.

    • for flows_into:

      • if specified, there must exist another CLUES element with that value as its id.

      • a CLUES element must not flow into itself.

      • multiple CLUES elements must not flow into the same other CLUES element.

      • clues may flow across pages, but only forward i.e a CLUES element must not flow into another on a page before it.

      • a cyclic flow (i.e a loop of CLUES elements that have no apparent source nor end) is invalid.

    • font is as for the TITLE element kind.

      • if not specified for an extension, it inherits that of its source.

  • SOLUTION: The puzzle’s solution in some form.

    • The width is INTRINSIC.

    • The height is DERIVED (for the same reason as the GRID element kind).

  • DIVIDER: A vertical/horizontal divider.

    • The dimension kinds depend on the orientation of its parent box:

      horizontal box

      vertical box

      width

      INTRINSIC

      WEIGHTED

      height

      WEIGHTED

      INTRINSIC

    • Has a pre-defined thickness.

    • NOTE: This is a DISPLAY element, NOT a CONTAINER.

  • SPACER: A blank space.

    • The width is WEIGHTED.

    • The height is WEIGHTED.

  • BOX: A container to organize elements.

    • Along each axis, the dimension kind is determined by a set of rules which take priority in the order in which they’re stated below; the first one whose condition is true determines the dimension kind.

    • Along the major axis:

      • If the box contains at least one element with a WEIGHTED dimension along that axis, the dimension is WEIGHTED.

      • If the box contains at least one element with a DERIVED dimension along that axis, the dimension is DERIVED.

      • Otherwise, the dimension is INTRINSIC.

    • Along the minor axis:

      • If the box contains at least one element with a DERIVED dimension along that axis, the dimension is DERIVED.

      • If the box contains at least one element with an INTRINSIC dimension along that axis, the dimension is INTRINSIC.

      • Otherwise, the dimension is WEIGHTED.

    • The data field has the format of a Box as earlier defined for the top-level page fields.

Template Config

The template loader accepts a configuration parameter consisting of the following fields:

  • clues_min_width (double): If greater than zero, then for every CLUES element within a horizontal BOX:

    • if minimum is specified, it is overriden with this value.

    • if maximum is also specified (i.e only if minimum is overriden), but less than this value, it is also overriden (to preserve the constraint maximum >= minimum).

    This is mostly a hack to help reduce the number of templates needed to cover various combinations of cases, but is actually effective enough.

Template Selection/Matching

A set of templates are pre-defined and used for printing puzzles. A template is selected based on the following criteria:

  • Puzzle kind: If a template’s puzzle_kinds field contains the target puzzle kind, it’s a match.

The pre-defined templates are loaded and tested in succession until one matches. The first to match (for all criteria) is selected. If none matches, selection fails. Ideally, there should be one and only one potential match per puzzle.

Areas For Improvement

  • [ ] Allow user-defined templates. This will require the following (amongst other things):

    • [ ] a version field in the format to prevent breakage when there are breaking changes to the format.

    • [ ] report errors during template loading, instead of treating them as programming errors.

  • [ ] A GUI to allow users to interactively create, modify and save layouts. This will require the following (amongst other things):

    • [ ] recursively serializing element structures to JSON (could use JsonBuilder + JsonGenerator).

Previous Next

© Copyright 2025, The Crosswords developers.

Built with Sphinx using a theme provided by Read the Docs.