Have ideas to improve npm?Join in the discussion! »

    @thi.ng/text-canvas
    TypeScript icon, indicating that this package has built-in type declarations

    0.7.9 • Public • Published

    text-canvas

    npm version npm downloads Twitter Follow

    This project is part of the @thi.ng/umbrella monorepo.

    About

    Text based canvas, drawing, tables with arbitrary formatting (incl. ANSI/HTML).

    Status

    ALPHA - bleeding edge / work-in-progress

    Search or submit any issues for this package

    Installation

    yarn add @thi.ng/text-canvas
    // ES module
    <script type="module" src="https://unpkg.com/@thi.ng/text-canvas?module" crossorigin></script>
    
    // UMD
    <script src="https://unpkg.com/@thi.ng/text-canvas/lib/index.umd.js" crossorigin></script>

    Package sizes (gzipped, pre-treeshake): ESM: 6.05 KB / CJS: 6.40 KB / UMD: 6.14 KB

    Dependencies

    Usage examples

    Several demos in this repo's /examples directory are using this package.

    A selection:

    Screenshot Description Live demo Source
    3D wireframe textmode demo Demo Source
    Textmode image warping w/ 16bit color output Demo Source

    API

    Generated API docs

    Canvas creation

    const c = canvas(width, height, format?, style?);

    Format identifiers

    The text canvas stores all characters in a Uint32Array with the lower 16 bits used for the UTF-16 code and the upper 16 bits for abitrary formatting data. The package provides its own format IDs which are tailored for the bundled ANSI & HTML formatters, but users are free to choose use any other system (but then will also need to implement a custom string formatter impl).

    The default format ID layout is as shown:

    format bit layout

    Most drawing functions accept an optional format arg, but a default format can also be set via setFormat(canvas, formatID).

    The following built-in format IDs are only compatible with these formatters:

    • FMT_ANSI16
    • FMT_HTML_INLINE_CSS
    • FMT_HTML_TACHYONS

    Custom formatters are discussed further below:

    Colors

    These color IDs MUST be prefixed with either FG_ (foreground) or BG_ (background):

    • BLACK
    • RED
    • GREEN
    • YELLOW
    • BLUE
    • MAGENTA
    • CYAN
    • GRAY
    • WHITE
    • LIGHT_GRAY
    • LIGHT_RED
    • LIGHT_GREEN
    • LIGHT_YELLOW
    • LIGHT_BLUE
    • LIGHT_MAGENTA
    • LIGHT_CYAN

    Variations

    • BOLD
    • DIM
    • UNDERLINE

    Combined formats

    Format IDs can be combined via the binary OR operator (|), e.g.:

    setFormat(canvas, FG_BLACK | BG_LIGHT_CYAN | BOLD | UNDERLINE);

    String conversion format presets

    Canvas-to-string conversion is completely customizable via the StringFormat interface and the following presets are supplied:

    • FMT_ANSI16 - translate built-in format IDs to 4bit ANSI escape sequences
    • FMT_ANSI256 - uses all 16 format bits for fg & bg colors (ANSI esc sequences)
    • FMT_ANSI_RAW - verbatim use of format IDs to ANSI sequences
    • FMT_HTML_INLINE_CSS - HTML <span> elements with inline CSS
    • FMT_HTML_TACHYONS - HTML <span> elements with Tachyons CSS class names
    • FMT_NONE - dummy formatter outputting plain text only (all format information discarded)
    // Terminal
    console.log(toString(canvas, FMT_ANSI16));
    // or
    console.log(toString(canvas, FMT_ANSI256));
    
    // Browser
    const el = document.createElement("pre");
    el.innerHTML = toString(canvas, FMT_HTML_TACHYONS);

    256 color ANSI format

    If targeting this output format, all 16 bits available for formatting information are used to encode 2x 8bit foreground/background colors. Therefore, none of the above mentioned preset color names and/or any additional formatting flags (e.g. bold, underline etc.) cannot be used. Instead, use the format256() function to compute a format ID based on given FG, BG colors.

    // deep purple on yellow bg
    textLine(canvas, 1, 1, "hello color!", format256(19, 226));

    ANSI256 color pallette

    Source: Wikipedia

    16bit color ANSI & HTML formats

    Similar to the above custom ANSI format, here all available 16 bits are used to store color information, in the standard RGB565 format (5bits red, 6bits green, 5bits blue). This also means, only either the text or background color(1) can be controlled and no other formatting flag (bold, underline etc.) are available.

    (1) In the ANSI version it's always only the text color.

    const el = document.createElement("pre");
    // format and assign text colors
    el.innerHTML = toString(canvas, FMT_HTML565());
    
    // assign bg colors
    el.innerHTML = toString(canvas, FMT_HTML565("background"));

    These formats are primarily intended for image display, see section below for examples...

    Ad-hoc formatting of strings

    String formatters can also be used in an ad-hoc manner, without requiring any of the other text canvas functionality.

    // create & use a HTML formatter
    defFormat(FMT_HTML_INLINE_CSS, FG_LIGHT_RED | BG_GRAY)("hello")
    // "<span style="color:#f55;background:#555;">hello</span>"
    
    // create & use an ANSI formatter
    defFormat(FMT_ANSI16, FG_LIGHT_RED | BG_GRAY)("hello")
    // "\x1B[91;100mhello\x1B[0m"
    
    // ANSI syntax sugar (same result as above)
    defAnsi16(FG_LIGHT_RED | BG_GRAY)("hello")
    // "\x1B[91;100mhello\x1B[0m"

    Furthermore, defFormatPresets() can be used to create formatting functions for all 16 preset foreground color IDs for a given string format strategy:

    const ansi = defFormatPresets(FMT_ANSI16);
    
    `${ansi.green("hello")} ${ansi.lightRed("world")}!`;
    // '\x1B[32mhello\x1B[0m \x1B[91mworld\x1B[0m!'
    
    const html = defFormatPresets(FMT_HTML_TACHYONS);
    
    `${html.green("hello")} ${html.lightRed("world")}!`;
    // '<span class="dark-green ">hello</span> <span class="red ">world</span>!'

    Stroke styles

    Built-in style presets:

    • STYLE_ASCII
    • STYLE_THIN
    • STYLE_THIN_ROUNDED
    • STYLE_DASHED
    • STYLE_DASHED_ROUNDED
    • STYLE_DOUBLE

    Functions:

    • beginStyle(canvas, style)
    • endStyle(canvas)

    Clipping

    All drawing operations are constrained to the currently active clipping rect (by default full canvas). The canvas maintains a stack of such clipping regions, each newly pushed one being intersected with the previous top-of-stack rect:

    • beginClip(canvas, x, y, w, h) - push new clip rect
    • endClip(canvas) - restore previous clip rect
    ┌──────────────────┐
    │ A                │
    │         ╔════════╗─────────┐
    │         ║        ║         │
    │         ║ A & B  ║         │
    │         ║        ║         │
    └─────────╚════════╝         │
              │                B │
              └──────────────────┘
    

    Drawing functions

    • line

    • hline

    • vline

    • circle

    • clear

    • fillRect

    • strokeRect

    Image functions

    • blit
    • resize
    • extract
    • scrollV
    • image / imageRaw / imageCanvas565 / imageString565
    import { RGB565 } from "@thi.ng/pixel";
    import { read } from "@thi.ng/pixel-io-netpbm";
    
    // resize non-proportionally (to compensate
    // for character aspect ratio, YMMV)
    const img = read(readFileSync("chroma-rings.ppm"))
      .resize(32, 32 / 2.25)
      .as(RGB565)
    
    // requires an ANSI 24bit compatible terminal
    console.log(imageString565(img));

    example image output in NodeJS REPL

    Text functions

    • textLine
    • textLines
    • textColumn (word wrapped)
    • textBox (word wrapped)

    Bars & bar charts

    The following are string builders only, draw result via text functions:

    • barHorizontal
    • barVertical
    • barChartHStr
    • barChartVStr

    Tables

    Tables support individual column width, automatic (or user defined) row heights, cell padding, as well as global and per-cell formats and the following border style options:

    Border style Result
    Border.ALL table
    Border.NONE table
    Border.H table
    Border.V table
    Border.FRAME table
    Border.FRAME_H table
    Border.FRAME_V table

    Table cell contents will be word-wrapped. By default, individual words longer than the configured cell width will be truncated, but can be forced to wrap by enabling the hard option (see example below).

    import { repeatedly } from "@thi.ng/transducers";
    import * as tc from "@thi.ng/text-canvas";
    
    // generate 20 random values
    const data = repeatedly(() => Math.random(), 20)
    // format as bar chart string
    const chart = tc.barChartHStr(4, data, 0, 1);
    
    // create text canvas
    const canvas = new tc.Canvas(64, 20);
    
    // create table
    tc.table(
        canvas,
        0,
        0,
        {
            // column defs
            cols: [{ width: 4 }, { width: 20 }, { width: 8 }],
            // default cell format
            format: tc.FG_BLACK | tc.BG_LIGHT_CYAN,
            // default format for header cells (1st row)
            formatHead: tc.FG_RED | tc.BG_LIGHT_CYAN | tc.BOLD | tc.UNDERLINE,
            // border line style
            style: tc.STYLE_DASHED_ROUNDED,
            // border mode
            border: tc.Border.ALL,
            // internal cell padding [h,v]
            padding: [1, 0],
            // hard word wrap
            hard: true,
        },
        // table contents (row major)
        // each cell either a string or RawCell object
        [
            ["ID", "Main", "Comment"],
            [
                "0001",
                { body: chart, format: tc.FG_BLUE | tc.BG_LIGHT_CYAN },
                "This is a test!"
            ],
            ["0002", "Random data plot", "Word wrapped content"],
            ["0003", { body: "More details...", height: 4 }, ""]
        ]
    );
    
    // output as ANSI formatted string
    console.log(tc.toString(canvas, tc.FMT_ANSI16));

    For even more detailed control, tables can also be pre-initialized prior to creation of the canvas via initTable() and then drawn via drawTable(). The initTable function returns an object also containing the computed table size (width, height keys) which can then be used to create a canvas with the required size...

    For convenience, the tableCanvas() function can be used to combine these steps and to create an auto-sized canvas with the rendered table as content.

    3D wireframe cube example

           ┌───┐
      ┌──────────────────────┐
      │ @thi.ng/text-canvas  │
      │ wireframe cube       │++++++++++
      │                      │          +++++++++++    ┌───┐
      │ x: 0.42              │                     ++++│ 6 │
      │ y: 0.30              │        ┌───┐ ++++++++   └───┘
      └──────────────────────┘++++++++│ 7 │+           +
               +         └───┘        └───┘            +
                +          +           +              +
                +          +           +              +
                 +         +           +             +
                 +         +          +              +
                 +          +         +              +
                  +         +         +             +
                  +         +         +             +
                   +        +        ┌───┐         +
                   +         +      +│ 3 │         +
                    +       ┌───┐+++ └───┘        +
                    +       │ 0 │       +         +
                     +      └───┘        +        +
                     +       +            +      +
                     +       +             +     +
                      +     +               +   +
                      +     +                +  +
                       +    +                 ┌───┐
                       +    +                 │ 2 │
                        +   +               ++└───┘
                        +   +            +++
                         + +           ++
                         + +        +++
                          ++      ++
    

    Code for this above example output (CLI version):

    import * as geom from "@thi.ng/geom";
    import * as mat from "@thi.ng/matrices";
    import * as tc from "@thi.ng/text-canvas";
    
    const W = 64;
    const H = 32;
    
    // create text canvas
    const canvas = new tc.Canvas(W, H, tc.BG_BLACK, tc.STYLE_THIN);
    
    // cube corner vertices
    const cube = geom.vertices(geom.center(geom.aabb(1))!);
    
    // edge list (vertex indices)
    const edges = [
        [0, 1], [1, 2], [2, 3], [3, 0], [4, 5], [5, 6],
        [6, 7], [7, 4], [0, 4], [1, 5], [2, 6], [3, 7]
    ];
    
    // animated parameters
    let rotx = 0;
    let roty = 0;
    
    // 3D transformation matrices
    const view = mat.lookAt([], [0, 0, 1], [0, 0, 0], [0, 1, 0]);
    const proj = mat.perspective([], 90, W / H, 0.1, 10);
    const viewp = mat.viewport([], 0, W, H, 0);
    
    setInterval(() => {
        tc.clear(canvas, true);
        // model rotation matrix
        const model = mat.concat(
            [],
            mat.rotationX44([], rotx += 0.01),
            mat.rotationY44([], roty += 0.03)
        );
        // combined model-view-projection matrix
        const mvp = mat.concat([], proj, view, model);
        // draw cube instances
        // project 3D points to 2D viewport (canvas coords)
        const pts = cube.map((p) => mat.project3([], mvp, viewp, p)!);
        // draw cube edges
        for (let e of edges) {
            const a = pts[e[0]];
            const b = pts[e[1]];
            tc.line(canvas, a[0], a[1], b[0], b[1], "+", tc.FG_WHITE | tc.BG_RED);
        }
        // draw vertex labels
        canvas.format = tc.FG_WHITE | tc.BG_BLUE;
        for (let i = 0; i < 8; i++) {
            const p = pts[i];
            tc.textBox(canvas, p[0] - 1, p[1] - 1, 5, 3, ` ${i} `);
        }
        tc.textBox(
            canvas,
            2, 1, 24, -1,
            `@thi.ng/text-canvas wireframe cube\n\nx: ${rotx.toFixed(2)}\ny: ${roty.toFixed(2)}`,
            {
                format: tc.FG_BLACK | tc.BG_LIGHT_CYAN,
                padding: [1, 0]
            }
        );
        // draw canvas
        console.clear();
        // output as ANSI formatted string
        console.log(tc.toString(canvas, tc.FMT_ANSI16));
        // output as plain text
        // console.log(tc.toString(canvas));
    }, 15);

    Authors

    Karsten Schmidt

    If this project contributes to an academic publication, please cite it as:

    @misc{thing-text-canvas,
      title = "@thi.ng/text-canvas",
      author = "Karsten Schmidt",
      note = "https://thi.ng/text-canvas",
      year = 2020
    }

    License

    © 2020 - 2021 Karsten Schmidt // Apache Software License 2.0

    Install

    npm i @thi.ng/text-canvas

    DownloadsWeekly Downloads

    11

    Version

    0.7.9

    License

    Apache-2.0

    Unpacked Size

    234 kB

    Total Files

    38

    Last publish

    Collaborators

    • avatar