npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@robin.berjon/notable

v1.0.1

Published

Parse and convert reMarkable .rm files

Downloads

247

Readme

notable

Parse and convert reMarkable tablet .rm files (version 6, software ≥ 3).

notable ships as two things:

  • lib/ — a Node.js library for reading and writing .rm files at the block/tree level
  • bin/notable — a CLI tool for converting .rm files to Markdown, SVG, and PDF

Installation

npm install @robin.berjon/notable

Or globally to use the CLI:

npm install -g @robin.berjon/notable

CLI Usage

notable [options] [input...]

Options:
  -f, --from <FORMAT>   Format to convert from (default: guess from filename)
  -t, --to <FORMAT>     Format to convert to (default: guess from filename)
  -o, --output <FILE>   Output filename (default: stdout)
  -v, --verbose         Increase verbosity
  -V, --version         Show version
  -h, --help            Show help

Available FORMAT values: rm, markdown, svg, pdf, blocks, blocks-data.

Examples

Convert a .rm file to Markdown (printed to stdout):

notable -t markdown note.rm

Convert a .rm file to SVG, writing to a file:

notable -t svg -o note.svg note.rm

Guess the output format from the file extension:

notable note.rm -o note.pdf

Convert a Markdown file to a .rm file:

notable -t rm note.md -o note.rm

Dump the internal block structure of a .rm file:

notable -t blocks note.rm
notable -t blocks-data note.rm   # omit point/byte data

Format notes

| Format | Read | Write | Notes | |--------|------|-------|-------| | rm | ✓ | ✓ | reMarkable binary file | | markdown | ✓ | ✓ | Text + highlighted passages | | svg | — | ✓ | Vector rendering of strokes | | pdf | — | ✓ | Pure JS, no external tools; multiple inputs become pages | | blocks | — | — | Debug dump (JSON) | | blocks-data | — | — | Debug dump without point data |

Library API

import {
  readBlocks, writeBlocks, readTree,
  simpleTextDocument,
  rmToMarkdown, rmToSvg, rmToPdf, rmsToPdf,
} from '@robin.berjon/notable';

Converters

The CLI's converters are part of the library:

import { readFileSync, writeFileSync } from 'node:fs';
import { rmToMarkdown, rmToSvg, rmsToPdf } from '@robin.berjon/notable';

const page = readFileSync('page.rm');
const markdown = rmToMarkdown(page);   // string
const svg = rmToSvg(page);             // string

// one PDF page per .rm buffer, all pure JS
const pdf = rmsToPdf([page1, page2, page3]);  // Buffer
writeFileSync('notebook.pdf', pdf);

rmToPdf(data) is the single-page shorthand; treesToPdf(trees) and treeToSvg(tree) accept already-parsed scene trees if you need to read the data once and export it several ways.

Reading blocks

import { readFileSync } from 'node:fs';
import { readBlocks } from '@robin.berjon/notable';

const data = readFileSync('note.rm');
for (const block of readBlocks(data)) {
  console.log(block.constructor.name);
}

readBlocks(buffer) accepts a Buffer and returns a generator of Block objects.

Writing blocks

import { writeBlocks } from '@robin.berjon/notable';

const buf = writeBlocks(blocks, { version: '3.2.2' });
// buf is a Buffer ready to write to a .rm file

The options object supports a version string to control which format features are emitted.

Reading the scene tree

import { readTree } from '@robin.berjon/notable';

const tree = readTree(data);
// tree.root     — root Group containing layers
// tree.rootText — Text block (keyboard text), or null
// tree.sceneInfo

Walking strokes

import { Line } from '@robin.berjon/notable';

for (const item of tree.walk()) {
  if (item instanceof Line) {
    console.log(`Stroke with ${item.points.length} points`);
  }
}

Creating a text document

import { simpleTextDocument, writeBlocks } from '@robin.berjon/notable';

const buf = writeBlocks(simpleTextDocument('Hello reMarkable!'));

Block types

| Class | Block type | Description | |-------|-----------|-------------| | AuthorIdsBlock | 0x09 | Maps author IDs to UUIDs | | MigrationInfoBlock | 0x00 | Migration metadata | | PageInfoBlock | 0x0A | Page statistics | | SceneTreeBlock | 0x01 | Tree structure entry | | TreeNodeBlock | 0x02 | Layer / group metadata | | SceneGroupItemBlock | 0x04 | Group child reference | | SceneLineItemBlock | 0x05 | Stroke (drawn line) | | SceneGlyphItemBlock | 0x03 | Highlighted text range | | SceneTextItemBlock | 0x06 | Text item in layer | | SceneTombstoneItemBlock | 0x08 | Deleted item marker | | RootTextBlock | 0x07 | Keyboard-entered text | | SceneInfo | 0x0D | Canvas / paper info | | UnreadableBlock | — | Unrecognised or corrupt block |

Scene items

import { PenColor, Pen, ParagraphStyle } from '@robin.berjon/notable';

// PenColor values: BLACK(0), GRAY(1), WHITE(2), YELLOW(3), GREEN(4),
//                 PINK(5), BLUE(6), RED(7), GRAY_OVERLAP(8), HIGHLIGHT(9), ...

// Pen values: BALLPOINT_1(2), BALLPOINT_2(15), FINELINER_1(4), FINELINER_2(17),
//             HIGHLIGHTER_1(5), HIGHLIGHTER_2(18), PENCIL_1(1), PENCIL_2(14), ...

// ParagraphStyle values: PLAIN(1), HEADING(2), BOLD(3), BULLET(4), BULLET2(5), ...

Text extraction

import { readTree, TextDocument } from '@robin.berjon/notable';

const tree = readTree(data);
if (tree.rootText) {
  const doc = TextDocument.fromSceneItem(tree.rootText);
  for (const paragraph of doc.contents) {
    console.log(paragraph.style.value, paragraph.toString());
  }
}

CRDT sequence

CrdtSequence is an ordered sequence backed by a topological sort of CRDT items:

import { CrdtSequence, CrdtSequenceItem, CrdtId } from '@robin.berjon/notable';

const seq = new CrdtSequence([
  new CrdtSequenceItem(new CrdtId(1, 1), new CrdtId(0, 0), new CrdtId(0, 0), 0, 'A'),
  new CrdtSequenceItem(new CrdtId(1, 2), new CrdtId(1, 1), new CrdtId(0, 0), 0, 'B'),
]);

seq.values(); // ['A', 'B']

SVG / PDF conversion notes

SVG conversion renders strokes with pressure, speed, and direction data but text rendering is basic. If text boxes are present:

  • Multi-line text may render on a single line
  • Stroke positions relative to text may be approximate

PDF conversion is pure JS with no external dependencies. Passing several .rm files produces a single PDF with one page per input:

notable -t pdf -o notebook.pdf page1.rm page2.rm page3.rm

Typed text uses the standard base-14 PDF fonts (Helvetica/Times, WinAnsi encoding) when possible. When the text contains characters outside WinAnsi — Cyrillic, Greek, extended Latin — the bundled Noto Sans/Noto Serif fonts (the same families the reMarkable itself renders typed text with, see fonts/) are embedded automatically, with a ToUnicode CMap so text extraction and search keep working. CJK is not covered (the Noto CJK fonts are ~20 MB per face).

Running tests

npm test

Tests cover binary round-trips of the test .rm files and the full test notebook, block serialisation, CRDT sequence ordering, text extraction, inline formatting, pen models, and SVG/PDF export (including embedded-font PDFs).

Acknowledgements

This is a JavaScript port of:

  • rmscene by Rick Lupton — the core .rm v6 parser
  • rmc — the CLI converter

The binary format was reverse-engineered with help from ddvk's reader.