Rough Highlighter

unlisted

by Tilak

Draws hand-drawn annotations over highlighted text, in reading view and live preview.

Updated 19d agoMIT
View on GitHub

Rough Highlighter

Hand-drawn highlight annotations for Obsidian, drawn with the geometry engine behind rough-notation.

Works in reading view and in live preview.

Syntax

In the noteResult
==text==The default style from settings
<mark class="rn-circle rn-red">text</mark>A red circle

Both are plain Obsidian highlights, so a note stays readable with the plugin turned off.

Type classes: rn-highlight, rn-underline, rn-box, rn-circle, rn-strike, rn-crossed, rn-bracket. Color classes: rn-yellow, rn-red, rn-green, rn-blue, rn-purple, rn-orange.

Right-click menu

Select text and right-click. A Highlight submenu holds:

  • Plain, which wraps the selection in ==.
  • The six palette colors, each using the default type from settings.
  • More styles..., which opens the full type and color list.
  • Remove highlight, shown only when the selection holds one.

Submenus need a recent Obsidian. On older builds the menu falls back to two flat items: Highlight and Highlight as....

Commands

The same actions, for hotkeys:

  • Highlight selection wraps the selection in ==.
  • Highlight selection as... picks a type and a color, and writes a <mark>.
  • Remove highlight from selection strips both forms.

Changing the palette

The colors are CSS variables. Put a snippet in your vault to change them:

body {
  --rn-yellow: #ffe066;
  --rn-red: #ff6b6b;
}

How it works

rough-notation's public annotate() API attaches its own SVG next to a DOM element. That works in reading view, but CodeMirror 6 owns the editor DOM and removes foreign nodes, and the API gives no control over the random seed, so every repaint would reshuffle the drawing.

So this plugin uses the library's lower-level renderAnnotation() instead, which draws into an SVG we own with a seed we choose. The seed comes from a hash of the highlighted text, so a phrase always gets the same wobble.

Two hosts feed that one renderer:

  • Reading view puts the SVG inside the <mark> itself. It moves with the text, so only a change to the mark's own box needs a redraw. An IntersectionObserver draws on first sight, and animates.
  • Live preview uses a CodeMirror layer. Obsidian already renders ==text== as span.cm-highlight and inline <mark> as a real element, so the layer measures those elements rather than parsing the source. CodeMirror recomputes the layer on every geometry change, which keeps the drawing glued to the text.

Known limits

  • Highlight ink is translucent and drawn behind the text. Every other type is drawn over it. In live preview that split needs two CodeMirror layers, one below the content and one above.

  • No draw-on animation in live preview. That view repaints on every geometry change, so an animation would keep restarting.

  • Wrapped text gets one shape per visual line. A box around three wrapped lines is three boxes, not one outline.

  • The annotation hides while the cursor is inside it, because Obsidian reveals the raw source there and the text shifts on every keystroke.

Install

The repo carries a built main.js, so you can copy main.js, manifest.json and styles.css into <vault>/.obsidian/plugins/rough-highlighter/, then turn the plugin on under Settings > Community plugins.

License

MIT. main.js bundles rough-notation, roughjs, hachure-fill and path-data-parser, all MIT, all Copyright (c) Preet Shihn. See THIRD-PARTY-NOTICES.md.

Build

npm install
npm run build   # type check, then bundle to main.js
npm run dev     # watch

OBSIDIAN_VAULT=/path/to/vault npm run deploy   # build, then copy into a vault

For plugin developers

Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.