Skip to main content

Custom Renderers

doclens uses a plugin architecture for file format support. You can register custom renderers for any file type.

Creating a Renderer

Extend BaseRenderer and implement the render method:

import { registerRenderer, BaseRenderer } from 'doclens';
import type { RendererContext } from 'doclens';

class MyCustomRenderer extends BaseRenderer {
async render(ctx: RendererContext): Promise<void> {
await super.render(ctx);

const text = new TextDecoder().decode(ctx.data);
const container = document.createElement('div');
container.className = 'my-renderer';
container.innerHTML = `<pre>${text}</pre>`;
ctx.container.appendChild(container);

// Enable search within the rendered content
this.contentEl = container;
}
}

registerRenderer('custom-type', MyCustomRenderer);

BaseRenderer Features

BaseRenderer provides built-in search support via a Highlighter instance. Once you set this.contentEl, search queries will automatically find and highlight text matches in the rendered DOM.

Methods You Can Override

MethodPurpose
render(ctx)Render the document into ctx.container
destroy()Clean up resources
search(query, options)Custom search logic
highlightMatch(index)Custom highlight behavior
clearHighlights()Custom clear behavior
getPageCount()Return total page count
getCurrentPage()Return current page number
goToPage(page)Navigate to a specific page
getOutline()Return bookmarks/TOC
getTextContent()Return full text content
rotate(degrees)Rotate the document

Registration

Register before loading a document. The fileType string matches against the detected file type:

import { registerRenderer } from 'doclens';

registerRenderer('my-format', MyCustomRenderer);

For lazy loading, use the factory variant:

import { registerRendererFactory } from 'doclens';

registerRendererFactory('my-format', async () => {
const { MyRenderer } = await import('./MyRenderer');
return new MyRenderer();
});

Example: Log File Viewer

class LogRenderer extends BaseRenderer {
async render(ctx: RendererContext): Promise<void> {
await super.render(ctx);

const text = new TextDecoder().decode(ctx.data);
const container = document.createElement('div');
container.style.fontFamily = 'monospace';
container.style.whiteSpace = 'pre-wrap';
container.style.padding = '16px';

const lines = text.split('\n');
for (const line of lines) {
const el = document.createElement('div');
el.textContent = line;

if (line.includes('ERROR')) {
el.style.color = '#ef4444';
el.style.fontWeight = 'bold';
} else if (line.includes('WARN')) {
el.style.color = '#f59e0b';
}

container.appendChild(el);
}

ctx.container.appendChild(container);
this.contentEl = container;
}
}

registerRenderer('log', LogRenderer);