rmrender

package
v1.3.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 16, 2026 License: AGPL-3.0 Imports: 8 Imported by: 0

README

rmrender

Package rmrender provides rendering of reMarkable .rm binary files to PDF format.

Overview

This package handles parsing and rendering of reMarkable .lines files (.rm format) which contain stroke data from the reMarkable tablet. The goal is to convert these binary stroke files into vector graphics in PDF format.

reMarkable .rm File Format

The .rm format is a binary format that stores drawing data. As of 2025, there are multiple versions:

  • Version 3: Original format, documented and supported by ddvk/rmapi/encoding/rm
  • Version 6: Current format used by reMarkable tablet (as of Dec 2025)
Version 6 Format Structure

Our test files use version 6 format:

Header: "reMarkable .lines file, version=6          " (43 bytes)
+ Binary data containing:
  - Layers
  - Lines/Strokes
  - Points with coordinates, pressure, tilt
  - Brush types and colors
Coordinate System
  • reMarkable dimensions: 1404 x 1872 pixels at 226 DPI
  • Physical size: ~157.6 x 210.3 mm (close to A5)
  • Origin: Top-left corner
  • PDF coordinates need transformation

Implementation Status

✅ Completed
  • Package structure created
  • Documentation established
🚧 In Progress
  • Version 6 format parser
  • Stroke rendering engine
⏳ Planned
  • Layer support
  • Template rendering
  • All brush types (pen, pencil, marker, highlighter, eraser)
  • Pressure sensitivity
  • Color support

Architecture

.rm file → Parser → Stroke Data → Renderer → PDF (pdfcpu)
Components
  1. Parser (parser.go)

    • Reads .rm binary format
    • Extracts layers, strokes, points
    • Handles version detection
  2. Renderer (renderer.go)

    • Converts strokes to PDF vector graphics
    • Handles coordinate transformation
    • Applies brush styles
  3. Types (types.go)

    • Data structures for strokes, layers, points
    • Brush definitions
    • Color constants

Brush Types

The reMarkable supports various brush types that need different rendering:

  • Ballpoint: Standard pen, solid lines
  • Marker: Broad strokes, semi-transparent
  • Fineliner: Thin, precise lines
  • Pencil: Textured, pressure-sensitive
  • Mechanical Pencil: Sharp, consistent
  • Brush: Calligraphy-style, pressure-sensitive
  • Highlighter: Wide, semi-transparent
  • Eraser: Removes underlying strokes
  • Erase Section: Removes entire stroke segments

Usage Example

import "github.com/platinummonkey/legible/internal/rmrender"

// Parse .rm file
data, err := os.ReadFile("page.rm")
if err != nil {
    log.Fatal(err)
}

parser := rmrender.NewParser()
document, err := parser.Parse(data)
if err != nil {
    log.Fatal(err)
}

// Render to PDF
renderer := rmrender.NewRenderer()
pdfData, err := renderer.RenderToPDF(document)
if err != nil {
    log.Fatal(err)
}

os.WriteFile("output.pdf", pdfData, 0644)

References

Format Specifications

Development Notes

Version 6 Parsing Challenge

The current ddvk/rmapi/encoding/rm package only supports version 3 format. Our test files use version 6. Options:

  1. Implement v6 parser from scratch - Most control, significant work
  2. Adapt existing v3 parser - May not support all v6 features
  3. Use external library - Dependency management issues (deionizedoatmeal/rmapi has module path conflicts)
  4. Hybrid approach - Use reference implementations for guidance, implement clean Go version

Recommendation: Implement v6 parser based on format specification and existing Python/C++ implementations as reference.

Testing Strategy

Test files in example/:

  • Test.rmdoc - Contains 2 pages with handwriting
  • .rm files are version 6 format
  • Use these for validation and visual comparison

TODO

  • Implement version 6 binary format parser
  • Create stroke data structures
  • Implement coordinate transformation
  • Basic line rendering to PDF
  • Support pressure-sensitive stroke width
  • Implement brush types
  • Layer support
  • Color support
  • Template rendering
  • Performance optimization
  • Comprehensive testing
  • Documentation and examples

Contributing

When implementing features:

  1. Start with basic stroke rendering
  2. Add brush types incrementally
  3. Test with example files
  4. Compare output with reMarkable app exports
  5. Document any format discoveries

Documentation

Overview

Package rmrender provides parsing and rendering of reMarkable .rm binary files to PDF.

Index

Constants

View Source
const (
	// Width in pixels (1404)
	Width = 1404

	// Height in pixels (1872)
	Height = 1872

	// DPI of the reMarkable screen
	DPI = 226
)

Dimensions defines the reMarkable screen dimensions

Variables

This section is empty.

Functions

func EstimateComplexity

func EstimateComplexity(doc *Document) int

EstimateComplexity returns an estimate of rendering complexity

Useful for progress reporting and performance optimization decisions.

func ExportMetadata

func ExportMetadata(doc *Document) map[string]interface{}

ExportMetadata extracts metadata about a document without rendering

Types

type BrushType

type BrushType int

BrushType represents the type of brush/tool used

const (
	// BrushBallpoint is a standard pen
	BrushBallpoint BrushType = 2

	// BrushMarker is a broad marker
	BrushMarker BrushType = 3

	// BrushFineliner is a thin, precise pen
	BrushFineliner BrushType = 4

	// BrushSharpPencil is a mechanical pencil
	BrushSharpPencil BrushType = 7

	// BrushTiltPencil is a pencil with tilt support
	BrushTiltPencil BrushType = 1

	// BrushBrush is a paintbrush/calligraphy brush
	BrushBrush BrushType = 0

	// BrushHighlighter is a semi-transparent highlighter
	BrushHighlighter BrushType = 5

	// BrushEraser removes strokes
	BrushEraser BrushType = 6

	// BrushEraseSection removes entire sections
	BrushEraseSection BrushType = 8

	// BrushCalligraphy is a calligraphy pen
	BrushCalligraphy BrushType = 21
)

func (BrushType) String

func (b BrushType) String() string

String returns the brush type name

type Color

type Color int

Color represents stroke colors

const (
	// ColorBlack is standard black ink
	ColorBlack Color = 0

	// ColorGray is gray ink
	ColorGray Color = 1

	// ColorWhite is white ink (eraser on black background)
	ColorWhite Color = 2

	// ColorYellow is yellow highlighter
	ColorYellow Color = 3

	// ColorGreen is green highlighter
	ColorGreen Color = 4

	// ColorPink is pink highlighter
	ColorPink Color = 5

	// ColorBlue is blue highlighter
	ColorBlue Color = 6

	// ColorRed is red ink
	ColorRed Color = 7

	// ColorGrayOverlay is gray overlay
	ColorGrayOverlay Color = 8
)

func (Color) RGB

func (c Color) RGB() (r, g, b uint8)

RGB returns the RGB values for this color (0-255)

func (Color) String

func (c Color) String() string

String returns the color name

type Document

type Document struct {
	Version Version
	Layers  []Layer
}

Document represents a complete .rm file with all its layers and strokes

func ParseFile

func ParseFile(filename string) (*Document, error)

ParseFile is a convenience function to parse a .rm file from disk

type Layer

type Layer struct {
	// Lines contains all the strokes in this layer
	Lines []Line
}

Layer represents a drawing layer in the document The reMarkable supports multiple layers that can be toggled on/off

type Line

type Line struct {
	// BrushType specifies what tool was used
	BrushType BrushType

	// Color of the stroke
	Color Color

	// BrushSize is the thickness/size setting (0-2 usually)
	BrushSize float32

	// Points contains all the points that make up this stroke
	Points []Point
}

Line represents a single stroke (pen stroke, eraser stroke, etc.)

type ParseResult

type ParseResult struct {
	Version     Version
	LayerCount  int
	StrokeCount int
	PointCount  int
	ParsedAt    time.Time
	ParseError  error
}

ParseResult contains metadata about the parsing operation

type Parser

type Parser struct {
	// contains filtered or unexported fields
}

Parser handles parsing of .rm binary files

func NewParser

func NewParser() *Parser

NewParser creates a new .rm file parser

func (*Parser) Parse

func (p *Parser) Parse(data []byte) (*Document, error)

Parse parses a .rm file from bytes and returns a Document

type Point

type Point struct {
	// X coordinate (0 to Width)
	X float32

	// Y coordinate (0 to Height)
	Y float32

	// Pressure (0.0 to 1.0) - affects stroke width
	Pressure float32

	// Speed of the stroke at this point (for texture simulation)
	Speed float32

	// Direction of stroke (for brush effects)
	Direction float32

	// Width at this point (may be calculated from pressure and brush size)
	Width float32

	// Tilt of the pen (for advanced brush effects)
	TiltX float32
	TiltY float32
}

Point represents a single point in a stroke with position and pressure

type RenderOptions

type RenderOptions struct {
	// RenderLayers controls which layers to render (nil = all)
	RenderLayers []int

	// BackgroundColor for the PDF page
	BackgroundColor Color

	// EnablePressure enables pressure-sensitive stroke width
	EnablePressure bool

	// EnableAntialiasing enables anti-aliasing for smoother strokes
	EnableAntialiasing bool

	// StrokeQuality controls rendering quality (higher = more points, smoother)
	StrokeQuality int // 1-10, default 5
}

RenderOptions configures the rendering process

func DefaultRenderOptions

func DefaultRenderOptions() *RenderOptions

DefaultRenderOptions returns sensible default rendering options

type Renderer

type Renderer struct {
	// contains filtered or unexported fields
}

Renderer handles rendering of parsed .rm documents to PDF

func NewRenderer

func NewRenderer() *Renderer

NewRenderer creates a new renderer with default options

func NewRendererWithOptions

func NewRendererWithOptions(opts *RenderOptions) *Renderer

NewRendererWithOptions creates a renderer with custom options

func (*Renderer) RenderLayers

func (r *Renderer) RenderLayers(doc *Document, layerIndices []int) ([]byte, error)

RenderLayers renders only specific layers from a document

func (*Renderer) RenderPage

func (r *Renderer) RenderPage(doc *Document, _ int) ([]byte, error)

RenderPage renders a single page with the given strokes

This is useful for multi-page documents where each .rm file represents one page.

func (*Renderer) RenderToPDF

func (r *Renderer) RenderToPDF(doc *Document) ([]byte, error)

RenderToPDF renders a parsed Document to PDF format

This creates a single-page PDF with all strokes rendered as vector graphics. The PDF will use the reMarkable dimensions (1404x1872 pixels at 226 DPI).

func (*Renderer) RenderWithTemplate

func (r *Renderer) RenderWithTemplate(_ *Document, _ string) ([]byte, error)

RenderWithTemplate renders a document with a template background

Templates include: Blank, Lined, Grid, Dots, etc.

type Version

type Version int

Version represents the .rm file format version

const (
	// VersionUnknown represents an unknown or unsupported version
	VersionUnknown Version = 0

	// Version3 is the original .rm format
	Version3 Version = 3

	// Version5 is an intermediate format version
	Version5 Version = 5

	// Version6 is the current format (as of Dec 2025)
	Version6 Version = 6
)

func (Version) String

func (v Version) String() string

String returns the version as a string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL