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

@docmosaic/core

v1.1.0

Published

Framework-agnostic document model, history, and browser-side PDF generation that powers the DocMosaic editor.

Readme

@docmosaic/core

Framework-agnostic TypeScript core for the DocMosaic editor — the document model, page-size + dimension helpers, a pure reducer with a history HOC, and the browser-side PDF generation pipeline. No React, no DOM coupling beyond what jspdf itself needs at call time.

Use this package directly when you want to build your own editor UI (web, native, headless), generate PDFs from scratch, or test logic without rendering anything. @docmosaic/react is one such UI built on top of it.

Install

bun add @docmosaic/core

Runtime dependencies:

  • jspdf@^2.5 (only loaded when generatePDF runs)
  • uuid@^11

Quick example

import { createDocument, createSection, generatePDF } from '@docmosaic/core';

const doc = createDocument();
doc.sections.push(createSection());

const blob = await generatePDF(doc.sections, {
    pageSize: doc.pageSize,
    orientation: doc.orientation,
    pages: doc.pages,
});

// Save it however you like.
const url = URL.createObjectURL(blob);

generatePDF reports progress via an optional third argument and throws Error('PDF generation cancelled') when its AbortSignal aborts — preserve that exact message if you wrap it; downstream callers match on it.

Reducer + history

The reducer is pure: no clock reads, no mutation, no side effects. Wrap it with withHistory to gain undo/redo over any sequence of actions.

import { createDocument, reducer, withHistory, type Action } from '@docmosaic/core';

const tracked = withHistory<ReturnType<typeof createDocument>, Action>(reducer);

let state = { present: createDocument(), past: [], future: [] };
state = tracked(state, { type: 'ADD_SECTION' });
state = tracked(state, { type: 'UPDATE_NAME', name: 'Invoice' });
state = tracked(state, { type: 'UNDO' }); // back to one section, default name
state = tracked(state, { type: 'REDO' }); // back to 'Invoice'

Action types are SCREAMING_SNAKE_CASE and accept an optional now: Date for clock-pinned tests.

Use case: building your own editor UI

The most common consumer is a UI layer:

  1. Seed state via createDocument() (or rehydrate one of your own).
  2. Drive mutations through reducer / withHistory — or useReducer(withHistory(reducer), …) in React.
  3. Render whatever you want from document.pages + document.sections. Geometry is stored in PDF points (72 DPI); convert to CSS pixels for display.
  4. Call generatePDF(document.sections, options) when the user hits "Download". Use estimatePDFSize for a cheap live size hint.

@docmosaic/react does exactly this — it's a useful reference for the wiring.

Public surface

Every export is documented with JSDoc; the generated declarations land at dist/index.d.ts after bun run build. The package's public surface:

  • Types — Document, Page, PageBackground, Section, ImageSection, ImageCrop, TextSection, ShapeSection, ShapeKind, DrawingSection, FrameSection, Stroke, Point, PageSize, PageOrientation, PageDimensions, MeasurementUnit, DragPosition, ResizeInfo, PDFGenerationOptions. See the Unit system concept doc for why geometry is in points and how to convert.
  • Container frames — resolveFrameParent (which frame, if any, contains a section), orderSectionsForRender (the shared back-to-front sort: zIndex, then frames behind their children, then array order). The new FrameSection type plus the optional SectionBase.parentFrameId and ImageSection.maskShape fields back the feature.
  • Page-size data — CUSTOM_PAGE_SIZES, PAGE_SIZE_LABELS, getPageDimensions, getPageDimensionsWithOrientation.
  • Dimension helpers — convertDimensions, formatDimensions, mmToPt, ptToMm.
  • PDF — generatePDF, estimatePDFSize, optimizeImageForPDF, processImagesForPDF, types GenerationOptions / GenerationProgress.
  • PNG — generatePNGs, types PNGGenerationOptions / PNGGenerationProgress.
  • Templates — exportTemplate, importTemplate, type DocumentTemplate.
  • Factories — createDocument, createPage, createSection.
  • State — reducer, withHistory, types Action, State, HistoryAction, HistoryState.

Image crop

ImageSection carries an optional crop field — a rectangle in PDF points relative to the section's bounding box. When set, only that region of the source image is rendered inside the section; the original imageUrl is preserved, so the operation is fully non-destructive.

import type { ImageSection } from '@docmosaic/core';

const section: ImageSection = {
    id: 'photo',
    type: 'image',
    x: 40,
    y: 40,
    width: 200,
    height: 150,
    page: 1,
    zIndex: 0,
    imageUrl: 'data:image/png;base64,...',
    crop: { x: 25, y: 20, width: 100, height: 80 },
};

Documents authored before this field existed still load — crop is optional, and omitting it takes the original byte-stable addImage path through jsPDF.

Frames

Two additive, optional features share the "frame" name:

  • Container frames — a FrameSection (type: 'frame') is a box that owns other sections. A child carries the frame's id in parentFrameId; resolveFrameParent(section, sections) computes which frame (if any) contains a section's center, and the editor uses it to adopt sections on drop. A frame draws an optional fill / stroke / radius behind its children — orderSectionsForRender is the single sort (shared by the canvas, PDF, and PNG paths) that guarantees the back-to-front order: zIndex, then frames before non-frames at equal zIndex, then array order.
  • Placeholder frames — an ImageSection with maskShape: 'rect' | 'circle' | 'line' is a shaped image slot; the image is clipped to the shape on render. Absent maskShape, the image takes the unchanged byte-stable path.
import { resolveFrameParent, orderSectionsForRender, type FrameSection } from '@docmosaic/core';

const frame: FrameSection = {
    id: 'card',
    type: 'frame',
    x: 40,
    y: 40,
    width: 300,
    height: 200,
    page: 1,
    zIndex: 0,
    fill: '#ffffff',
    stroke: '#e5e5e5',
};
// `child.parentFrameId === frame.id` links a section to the frame.
const drawOrder = orderSectionsForRender(doc.sections.filter((s) => s.page === 1));

Both fields are optional, so documents authored before frames existed load and render unchanged.

Templates

A template is a Document snapshot. Use exportTemplate to serialize one to a JSON string (stable key order for diff-friendly output) and importTemplate to load it back with rehydrated createdAt / updatedAt dates.

import { exportTemplate, importTemplate } from '@docmosaic/core';

const json = exportTemplate(doc);
// Persist `json` anywhere — local storage, a file, a URL hash.
const restored = importTemplate(json); // throws on shape mismatch

PNG export

generatePNGs renders one PNG Blob per page via a 2D canvas pipeline (mirrors the PDF layer order). Use it alongside generatePDF when you want per-page raster output. The PNG and PDF pipelines aren't expected to be pixel-identical — they share layout but go through different rasterizers.

import { generatePNGs } from '@docmosaic/core';

const pngs = await generatePNGs(doc.sections, {
    pageSize: doc.pageSize,
    orientation: doc.orientation,
    pages: doc.pages,
    scale: 2, // 144 DPI; default is 2
});

License

MIT.