prawn

    Prawn's layout behaviour for MoonBit on moonbitlang/pagelayout: the document cursor and bounds, formatted text, font metrics, prawn-svg and prawn-table sizing

    pdf
    prawn
    svg
    typesetting
    Download zip
    Author
    Version
    0.1.0
    License
    MIT
    Last updated
    12 hours ago
    Downloads
    14

    #bobzhang/prawn

    The layout of Prawn 2.4.0, the Ruby PDF library Asciidoctor PDF 2.3.27 lays documents out with, for MoonBit, drawing through moonbitlang/pagelayout. It reproduces Prawn's measurements and decisions closely enough that bobzhang/asciidoctor-pdf matches Ruby Asciidoctor PDF's output.

    packagewhat it is
    bobzhang/prawnthe document cursor, bounds, columns and pages (Flow), formatted text (Fragment, Style) and its line wrapping (typeset_lines, Flow::typeset, Flow::typeset_box), font metrics of TrueType and the standard AFM fonts (FontCatalog, Face) with fallback fonts and icon fonts
    bobzhang/prawn/svga port of prawn-svg 0.34.2: SVG documents rendered into pagelayout graphic operations
    bobzhang/prawn/tableprawn-table 0.2.2's sizing: column widths and row heights

    Where Prawn calls back into Asciidoctor PDF (its fragment callbacks for inline images and destinations, and its font policy), a Flow takes Hooks from the document:

    let catalog = FontCatalog::load(files)
    let flow = Flow::new(catalog, setup, hooks={ ..Hooks::new(), base_family: "Noto Serif" })
    flow.start_new_page()
    flow.typeset(fragments, line_metrics(1.15, flow.font(style), style.size), style)

    The module is developed in the asciidoctor.mbt repository together with the PDF backend, and checked by its comparison with Ruby Asciidoctor PDF (scripts/pdf_compare.mbtx).

    #License

    MIT, except for the third-party material listed in NOTICE: the prawn-svg port (MIT, svg/LICENSE) and the parts adapted from Prawn and prawn-table (Matz's terms for Ruby, LICENSES/LICENSE-prawn).

    Alignment

    pub(all) enum Alignment {
    Left
    Center
    Right
    Justify
    } derive(Eq)

    Alignment::equal

    fn Alignment::equal(Alignment, Alignment) -> Bool

    Alignment::not_equal

    fn Alignment::not_equal(x : Alignment, y : Alignment) -> Bool

    CodeBackground

    pub(all) enum CodeBackground {
    Gap(Double, Bool)
    FullLine
    } derive(Eq)

    How far the background of a fragment of a highlighted source block reaches, when not just its glyph box.

    CodeBackground::equal

    CodeBackground::not_equal

    fn CodeBackground::not_equal(x : CodeBackground, y : CodeBackground) -> Bool

    Columns

    pub(all) struct Columns {
    count : Int
    stride : Double
    current : Int
    }

    A column box (Prawn's ColumnBox with reflow_margins): the bounds are split into count columns gap apart; running out of room moves to the next column at the box's top, and past the last one to a new page, where the box starts at the page's top.

    Face

    pub(all) struct Face {
    metrics :
    FaceMetrics

    ascender : Int
    descender : Int
    line_gap : Int
    kern : Map[Int, Double]
    standard : Bool
    }

    One face of the font catalog, with the metrics Prawn (the layout engine behind Ruby asciidoctor-pdf) derives from it. Prawn works in 1/1000 em and truncates every metric to an integer there; so does this.

    Face::ascender_pt

    fn Face::ascender_pt(self : Face, size : Double) -> Double

    Face::descender_pt

    fn Face::descender_pt(self : Face, size : Double) -> Double

    Positive depth below the baseline.

    Face::height_pt

    fn Face::height_pt(self : Face, size : Double) -> Double

    Prawn's font.height: ascender − descender + line gap.

    Face::kerning

    fn Face::kerning(self : Face, left : Int, right : Int) -> Double

    Kerning between two codepoints, 1/1000 em (negative tightens).

    Face::line_gap_pt

    fn Face::line_gap_pt(self : Face, size : Double) -> Double

    Face::prawn_width

    fn Face::prawn_width(self : Face, codepoint : Int) -> Double

    Prawn's glyph width in 1/1000 em (truncated), used to break lines as Prawn does.

    Flow

    pub(all) struct Flow {
    catalog : FontCatalog
    setup : PageSetup
    hooks : Hooks
    model :
    PageModel

    scratch : Bool
    page_top : Double
    top : Double
    bottom : Double
    page : Int
    y : Double
    left : Double
    right : Double
    columns : Columns?
    last_line : (Line, Int, Double)?
    last_line_first : Bool
    taring : Int
    inked : Bool
    first_break_empty : Bool?
    page_margins : (Int) -> (Double, Double)?
    margin_left : Double
    margin_right : Double
    page_width_now : Double
    page_height_now : Double
    }

    Flow::add_anchor

    fn Flow::add_anchor(self : Flow, name : String, x : Double, y : Double) -> Unit

    Flow::advance_page

    fn Flow::advance_page(self : Flow) -> Unit

    Move to the next page, creating it unless an earlier pass already did (a block's background revisits pages its content spilled onto); in a column box, to the next column first (Prawn's move_past_bottom).

    Flow::at_page_top

    fn Flow::at_page_top(self : Flow) -> Bool

    Flow::column_box

    fn Flow::column_box(self : Flow, count : Int, gap : Double, body : () -> Unit) -> Unit

    Lay out body in count columns gap apart, starting at the cursor (Prawn's column_box with reflow_margins: true); the cursor ends where the first column did, or, when the box went on past it, at the bottom of the last one.

    Flow::cursor

    fn Flow::cursor(self : Flow) -> Double

    Space left above the bottom margin (Prawn's cursor).

    Flow::dest_x

    fn Flow::dest_x(self : Flow) -> Double

    A named destination for the block at the cursor (asciidoctor-pdf's add_dest_for_block).

    Flow::fits

    fn Flow::fits(self : Flow, height : Double, y? : Double) -> Bool

    Whether height fits below the cursor (prawn-table's fits_on_page?).

    Flow::font

    fn Flow::font(self : Flow, style : Style) -> Face

    Flow::go_to_page

    fn Flow::go_to_page(self : Flow, index : Int) -> Unit

    Make page index the current page (Prawn's go_to_page), in its margins; the cursor is left where it is.

    Flow::height_of

    fn Flow::height_of(self : Flow, fragments : Array[Fragment], metrics : LineMetrics, base : Style, normalize_line_height? : Bool) -> Double

    The height typeset would take for fragments in the current bounds (asciidoctor-pdf's height_of_typeset_text), ignoring page breaks.

    Flow::indent

    fn[T] Flow::indent(self : Flow, left : Double, right : Double, body : () -> T) -> T

    Flow::ink_line

    fn Flow::ink_line(self : Flow, line : Line, x0 : Double, baseline : Double) -> Unit

    Draw one placed line on the current page.

    Flow::line_items

    fn Flow::line_items(self : Flow, line : Line, x0 : Double, baseline : Double) -> Array[
    PageItem
    ]

    The items that draw one placed line with its left edge at x0 and its baseline at baseline: backgrounds, glyph runs, decorations, link regions and anchors.

    Flow::margin

    fn Flow::margin(self : Flow, amount : Double) -> Unit

    asciidoctor-pdf's margin: nothing at the top of a page, a move down when it fits, else a new page.

    Flow::mark

    fn Flow::mark(self : Flow) -> Mark

    Flow::move_down

    fn Flow::move_down(self : Flow, amount : Double) -> Unit

    Flow::new

    fn Flow::new(catalog : FontCatalog, setup : PageSetup, hooks? : Hooks, scratch? : Bool) -> Flow

    A flow over pages set up as setup, its text set from catalog, the document answering hooks; scratch for a dry run.

    Flow::on_verso

    fn Flow::on_verso(self : Flow) -> Bool

    Whether the current page is a verso page (an even page number).

    Flow::pad_bottom

    fn Flow::pad_bottom(self : Flow, amount : Double) -> Unit

    The bottom padding of a padded box: a move down when it fits, else the next page (Prawn's move_past_bottom).

    Flow::page_h

    fn Flow::page_h(self : Flow) -> Double

    The current page's height (the PDF's y axis is measured up from its bottom).

    Flow::push

    fn Flow::push(self : Flow, item :
    PageItem
    ) -> Unit

    Flow::start_new_page

    fn Flow::start_new_page(self : Flow, layout? : String, size? : (Double, Double)) -> Unit

    Flow::tared

    fn Flow::tared(self : Flow, body : () -> Unit) -> Unit

    Ink body as tared content: captions and backgrounds, which do not keep a block on a page by themselves.

    Flow::text

    fn Flow::text(self : Flow) -> TextContext

    What the flow sets text with.

    Flow::to_last_column

    fn Flow::to_last_column(self : Flow) -> Unit

    In a column box, move to its last column (Prawn's bounds.current_column = bounds.last_column), so the next page break starts a new page.

    Flow::typeset

    fn Flow::typeset(self : Flow, fragments : Array[Fragment], metrics : LineMetrics, base : Style, align? : Alignment, bottom_gutter? : Double, hanging_indent? : Double, normalize_line_height? : Bool, first_line_style? : Style, text_indent? : Double) -> Unit

    Ink formatted text at the cursor, flowing onto new pages as needed — asciidoctor-pdf's typeset_text over Prawn's text.

    Flow::typeset_box

    fn Flow::typeset_box(self : Flow, fragments : Array[Fragment], metrics : LineMetrics, base : Style, align~ : Alignment, x~ : Double, width~ : Double, height~ : Double, text_indent~ : Double) -> (Array[Fragment], Double?)

    Prawn's formatted text box with a height (text_box): the lines of fragments set width wide from x (the first one text_indent further in, Prawn's indent_paragraphs), the first hanging from the cursor plus the line padding, as many as fit in height. Returns the fragments that did not fit and the baseline of the last line inked (None when none was); the cursor does not move.

    Flow::underlay

    fn Flow::underlay(self : Flow, start : Mark, make : (Double, Double, Bool, Bool) -> Array[
    PageItem
    ]) -> Unit

    Slide make(page, top, bottom, first, last) underneath everything inked since start, on every page from start to the cursor: the painter's order of a background drawn before its content, without a dry run.

    Flow::width

    fn Flow::width(self : Flow) -> Double

    Flow::width_of

    fn Flow::width_of(self : Flow, text : String, style : Style) -> Double

    Prawn's width_of a plain string in style's font, trailing spaces included (asciidoctor-pdf's rendered_width_of_string): the truncated glyph widths, kerned.

    FontCatalog

    pub struct FontCatalog {
    faces : Map[String, Face]
    registry :
    FontRegistry

    legacy_icons : Map[String, String]
    // private fields
    }

    The font catalog of the theme: family → the four styles, and the pagelayout registry holding them, which the renderer draws with; and the icon sets (icons=font), each a font family of its own.

    FontCatalog::has_icons

    fn FontCatalog::has_icons(self : FontCatalog) -> Bool

    Whether the catalog can draw font icons (it has icon sets).

    FontCatalog::icon_glyph

    fn FontCatalog::icon_glyph(self : FontCatalog, set : String, name : String) -> String?

    The glyph of icon name in icon set set, when the catalog has both.

    FontCatalog::load

    fn FontCatalog::load(files : Array[(String, Bool, Bool, Bytes)], icon_sets? : Array[IconSet], legacy_icon_mapping? : String, fallbacks? : Array[String]) -> FontCatalog raise

    Load a catalog from font files: (family, bold, italic, TrueType bytes), with the icon sets icons=font draws from, asciidoctor-pdf's mapping of Font Awesome 4 icon names (fa-legacy-mapping.yml), and the families a character the text's font lacks is looked for in (fallbacks).

    FontCatalog::svg_families

    fn FontCatalog::svg_families(self : FontCatalog) -> Array[(String, Array[String])]

    The families Prawn knows (font_families), with their styles: the catalog's (not the icon sets'), and Prawn's built-in AFM families.

    Fragment

    pub(all) struct Fragment {
    text : String
    style : Style
    anchor : String?
    }

    A run of text in one style. An anchor fragment has no text and marks a named destination at its position.

    Hooks

    pub(all) struct Hooks {
    base_family : String
    unknown_font : (String) -> Unit
    image_size : (Int, Double, Double) -> (Double, Double, Bool)
    image_items : (Flow, Int, Double, Double, Double, Double) -> Array[
    PageItem
    ]
    anchor_inked : (String, Int) -> Unit
    fragment_inked : (Int) -> Unit
    }

    What the document does where Prawn calls back into asciidoctor-pdf: the fragment callbacks it registers (InlineImageArranger and InlineImageRenderer for inline images, InlineDestinationMarker for anchors), and its font policy. One per document: a font catalog may be shared between documents, these may not.

    Hooks::new

    fn Hooks::new(base_family? : String) -> Hooks

    Hooks for a document without inline images, whose unknown fonts go unreported.

    IconSet

    pub(all) struct IconSet {
    name : String
    font : Bytes
    legend : String
    }

    An icon font of prawn-icon (data/fonts/<set>/): the set's name (fas, far, fab, fi, pf), its TrueType font and its legend, which names each glyph: name: glyph lines (fire: "…", under the set's key in prawn-icon's <set>.yml; the bundled @icons.fonts() have their own tables, <set>/glyphs.txt, made from the fonts' projects).

    Item

    pub(all) struct Item {
    unit : Int
    fragment : Int
    fit : Double
    draw : Double
    kern : Double
    hard_break : Bool
    anchor : String?
    offset : Int
    }

    Line

    pub(all) struct Line {
    runs : Array[LineRun]
    anchors : Array[(String, Double)]
    fit_width : Double
    ascender : Double
    descender : Double
    height : Double
    empty_links : Array[(Double, Style, Face)]
    images : Array[(Double, Int, Double, Double, Bool, Style, Face)]
    marks : Array[(Double, Style, Face)]
    }

    LineMetrics

    pub(all) struct LineMetrics {
    leading : Double
    padding_top : Double
    padding_bottom : Double
    }

    Prawn's line metrics for a line height factor (asciidoctor-pdf's calc_line_metrics).

    LineRun

    pub(all) struct LineRun {
    fragment : Int
    text : String
    style : Style
    face : Face
    x : Double
    advances : Array[Double]
    width : Double
    }

    One placed run of a line; x is relative to the line's left edge.

    Mark

    pub(all) struct Mark {
    page : Int
    y : Double
    item : Int
    }

    A point in the flow: page, cursor, and how many items the page held, so backgrounds can be slid underneath content inked after it.

    PageSetup

    pub(all) struct PageSetup {
    width : Double
    height : Double
    margin_top : Double
    margin_right : Double
    margin_bottom : Double
    margin_left : Double
    }

    The page size and margins a document starts with (asciidoctor-pdf's build_pdf_options: page_size, page_layout, page_margin), in points.

    Style

    pub(all) struct Style {
    family : String
    size : Double
    bold : Bool
    italic : Bool
    color :
    Color

    link :
    LinkTarget
    ?
    background :
    Color
    ?
    script : Int
    border_offset : Double
    underline : Bool
    strike : Bool
    wj : Bool
    image : Int
    callback : Int
    doc_bold : Bool
    doc_italic : Bool
    font_set : Bool
    text_transform : String?
    code_background : CodeBackground?
    linenum : Bool
    line_mark : Bool
    wrap_break : Bool
    } derive(Eq)

    Character formatting of a fragment.

    Style::equal

    fn Style::equal(Style, Style) -> Bool

    Style::face_style

    fn Style::face_style(self : Style) -> (Bool, Bool)

    The (bold, italic) face a fragment is set in, as Prawn's arranger picks it (apply_font_settings): a fragment whose markup names a font or a bold or italic style takes exactly those styles; any other fragment is set in the document font, whatever its style.

    Style::not_equal

    fn Style::not_equal(x : Style, y : Style) -> Bool

    TextContext

    pub(all) struct TextContext {
    catalog : FontCatalog
    hooks : Hooks
    }

    What text is set with: the font catalog and the document's hooks.

    TextContext::face

    fn TextContext::face(self : TextContext, family : String, bold : Bool, italic : Bool) -> Face

    build_items

    fn build_items(fragments : Array[Fragment], ctx : TextContext, available? : Double) -> (Array[Item], Array[Face])

    dashed_hrule

    fn dashed_hrule(x1 : Double, x2 : Double, y : Double, width : Double, color :
    Color
    ) -> Array[
    PageItem
    ]

    A dashed horizontal rule (dashes and gaps four times its width, as Prawn's dash in asciidoctor-pdf's dashed style), as filled segments.

    default_font_files

    fn default_font_files() -> Array[(String, Bool, Bool, String)]

    The file names of the default theme's catalog (asciidoctor-pdf's data/fonts): (family, bold, italic, file).

    default_icon_font_files

    fn default_icon_font_files() -> Array[(String, String, String)]

    The icon sets of prawn-icon 3.0.0 (its data/fonts): (set, font file, legend file), relative to that directory.

    face_key

    fn face_key(family : String, bold : Bool, italic : Bool) -> String

    hrule

    fn hrule(x1 : Double, x2 : Double, y : Double, width : Double, color :
    Color
    ) ->
    PageItem

    A horizontal rule width thick centred on y.

    line_extent

    fn line_extent(line : Line, base_face : Face, size : Double, normalize_line_height : Bool) -> (Double, Double, Double)

    The ascender, descender and height a placed line takes: its fragments' largest, or the document font's for an empty line. With normalize_line_height, the document font's count on every line, as asciidoctor-pdf's arranger puts a zero-width space in that font at the start of each line (so a line of code in a list takes the prose line's height, not the smaller one of the monospace font).

    line_metrics

    fn line_metrics(factor : Double, face : Face, size : Double) -> LineMetrics

    pdf_anchor_name

    fn pdf_anchor_name(id : String) -> String

    The PDF destination name for an id (asciidoctor-pdf's derive_anchor_from_id): the id itself when it is ASCII, else 0x and the hex of its UTF-8 bytes.

    rect_item

    fn rect_item(x : Double, y : Double, w : Double, h : Double, fill? :
    Color
    , stroke? :
    Color
    , stroke_width? : Double) ->
    PageItem

    rgb_hex

    fn rgb_hex(value : Int) ->
    Color

    source_wrap

    fn source_wrap(fragments : Array[Fragment], ctx : TextContext, width : Double) -> Array[Fragment]?

    asciidoctor-pdf's SourceWrap: fragments (a numbered source block, each line starting with its line number, Style::linenum) with every line that wraps in width broken where it wraps: a line break, then the number's blank (a no-break space and spaces as wide as the number, in no style of its own), the line's highlight mark if it has one (Style::line_mark), and the rest of the line without the blanks it starts with. A number (or blank) wider than the line breaks before its trailing blanks, the line's text going on a line of its own, as Prawn breaks it. Each pass breaks the first line that wraps, consuming some of the source's text or of a number's blanks, so the passes end.

    None when a line cannot fit, as Prawn raises Prawn::Errors::CannotFit: its number (or blank) without its blanks is wider than the line, or it would hold nothing else.

    typeset_lines

    fn typeset_lines(fragments : Array[Fragment], ctx : TextContext, width : Double, align : Alignment, first_width? : Double, indent_paragraphs? : Bool) -> Array[Line]

    Break fragments into lines of at most width (the first line first_width, and with indent_paragraphs also each line after a hard break: Prawn's indent_paragraphs starts a paragraph there) and place them.

    vrule

    fn vrule(x : Double, y1 : Double, y2 : Double, width : Double, color :
    Color
    ) ->
    PageItem

    A vertical rule width thick centred on x.