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

web-to-print

v0.2.0

Published

Web-to-print component library for logo upload, rendering, and editing

Readme

web-to-print

A Stencil.js Web Components library for promotional product customization. Upload logos, preview them on products, and edit layouts — all client-side, no server required.

Components

| Component | Description | Encapsulation | |---|---|---| | <wtp-logo-upload> | Logo upload with drag-and-drop, URL fetch, format detection, print validation, and optional background removal | Shadow DOM | | <wtp-logo-renderer> | Static logo-on-product rendering via Fabric.js StaticCanvas | Scoped | | <wtp-editor> | Interactive canvas editor with toolbar, text tool, and JSON serialization via Fabric.js | Scoped |

Components communicate via CustomEvents and can be used standalone or composed together.

Installation

npm install web-to-print

Lazy-loading (recommended)

<script type="module" src="web-to-print/loader/index.js"></script>

Or with a bundler:

import { defineCustomElements } from 'web-to-print/loader';
defineCustomElements();

Standalone custom elements

import 'web-to-print/wtp-logo-upload';
import 'web-to-print/wtp-editor';
import 'web-to-print/wtp-logo-renderer';

Quick Start

<!-- 1. Upload a logo -->
<wtp-logo-upload id="uploader"></wtp-logo-upload>

<!-- 2. Render it on a product -->
<wtp-logo-renderer id="renderer" width="400" height="300"></wtp-logo-renderer>

<!-- 3. Open an interactive editor -->
<wtp-editor id="editor" width="800" height="600"></wtp-editor>

<script>
  const uploader = document.getElementById('uploader');
  const renderer = document.getElementById('renderer');
  const editor = document.getElementById('editor');

  uploader.addEventListener('wtpLogoValidated', async (e) => {
    const logo = e.detail;

    // Place logo on the static renderer
    renderer.logos = [{
      id: 'logo-1',
      dataUrl: logo.dataUrl,
      transform: { x: 200, y: 150, scaleX: 0.5, scaleY: 0.5, angle: 0 },
    }];

    // Or add it to the interactive editor
    await editor.addLogo(logo);
  });
</script>

Component API

<wtp-logo-upload>

Validates uploaded files against configurable print-quality rules (format, DPI, dimensions, file size). Supports file picker, drag-and-drop, and URL fetch.

Properties

| Property | Attribute | Type | Default | Description | |---|---|---|---|---| | config | — | LogoValidationConfig | See below | Validation rules for uploaded logos | | accept | accept | string | 'image/png,image/jpeg,image/svg+xml,image/tiff,image/avif,application/pdf' | Accepted MIME types for file input | | multiple | multiple | boolean | false | Allow multiple file uploads | | disabled | disabled | boolean | false | Disable the upload component | | enableBackgroundRemoval | enable-background-removal | boolean | false | Enable client-side background removal for raster images | | bgRemovalConfig | — | Partial<BgRemovalConfig> | {} | Configuration for the color-based background removal algorithm | | labels | — | Partial<LogoUploadLabels> | {} | Override user-facing strings (see Localizing text) |

Default validation config:

{
  minDpi: 300,
  maxFileSize: 50 * 1024 * 1024, // 50 MB
  minWidth: 100,
  minHeight: 100,
  allowedFormats: ['png', 'jpeg', 'svg', 'pdf', 'tiff', 'avif'],
}

Events

| Event | Detail | Description | |---|---|---| | wtpLogoValidated | LogoData | Fires when a logo passes validation | | wtpLogoSelected | LogoData | Fires when the customer picks a logo: after a fresh upload and on every gallery click, including the already-selected logo — wire this to addLogo() for click-to-place. Not fired by the auto-reselection after a removal | | wtpLogoRejected | { file: File; issues: LogoValidationIssue[] } | Fires when a logo fails validation | | wtpLogoProcessing | boolean | Fires when processing state changes (true = busy) |

Background Removal

When enable-background-removal is set and a raster image (PNG, JPEG, TIFF, AVIF) is uploaded, the user is presented with a choice between the original image and a version with the background removed. SVG and PDF files skip this step entirely.

The background removal runs entirely client-side using a color-based flood-fill algorithm with zero external dependencies:

  1. Samples all pixels along the 4 edges of the image
  2. Finds the dominant edge color (quantized into buckets)
  3. BFS flood-fills from edge pixels within tolerance distance of the background color
  4. Sets matched pixels to transparent — interior regions of the same color (e.g. white text) are preserved

Configure via bgRemovalConfig:

| Option | Type | Default | Description | |---|---|---|---| | tolerance | number | 40 | Color distance threshold (0-255, Euclidean RGB distance) | | minEdgeRatio | number | 0.3 | Minimum fraction of edge pixels sharing a color to count as background |

<wtp-logo-upload enable-background-removal></wtp-logo-upload>

URL Input

Users can also paste an HTTPS URL to fetch a remote logo. The fetched file goes through the same validation pipeline. Non-HTTPS URLs are rejected, and CORS errors are displayed with a clear message.


<wtp-logo-renderer>

Non-interactive canvas for rendering logos on product images. Uses Fabric.js StaticCanvas for lightweight rendering without user interaction.

Properties

| Property | Attribute | Type | Default | Description | |---|---|---|---|---| | productImage | product-image | string \| undefined | undefined | Product background image URL | | width | width | number | 600 | Canvas width in pixels | | height | height | number | 400 | Canvas height in pixels | | logos | — | PlacedLogo[] | [] | Array of logos to place on the canvas | | backgroundColor | background-color | string | '#ffffff' | Canvas background color | | printArea | — | PrintArea \| undefined | undefined | Auto-fits logos without an explicit transform into the area (0–1 relative coords) |

Events

| Event | Detail | Description | |---|---|---| | wtpRenderComplete | { dataUrl: string } | Fires when all logos have been rendered | | wtpRenderError | { message: string } | Fires on rendering error |

Methods

| Method | Signature | Description | |---|---|---| | exportImage | (format?: 'png' \| 'jpeg', quality?: number) => Promise<string> | Export canvas as a data URL |


<wtp-editor>

Interactive canvas editor with a built-in toolbar for adding text, changing fonts, deleting objects, and exporting. Uses Fabric.js Canvas for full interactivity (drag, resize, rotate).

Properties

| Property | Attribute | Type | Default | Description | |---|---|---|---|---| | width | width | number | 800 | Canvas width in pixels | | height | height | number | 600 | Canvas height in pixels | | views | — | ArticleView[] | [] | The article's decoration options (Veredelungen). Every view needs a stable id | | articleId | article-id | string | '' | Written into the exported envelope | | activeViewId | active-view-id | string \| undefined | undefined | Decoration currently edited. Defaults to the isDefault view, else the first | | showViewStrip | show-view-strip | boolean | true | Built-in decoration strip (hidden for a single decoration). Each thumbnail shows a hover/focus tooltip with the decoration's print method and area | | initialLogo | — | LogoData \| undefined | undefined | Logo picked in the catalog, placed once into the decoration the editor opens on — and nowhere else. Ignored when initialState is set | | productImage | product-image | string \| undefined | undefined | Deprecated — single-decoration fallback, used only when views is empty | | initialState | initial-state | string \| undefined | undefined | JSON-serialized initial editor state | | fonts | — | string[] | ['Arial', 'Helvetica', 'Times New Roman', 'Georgia', 'Verdana'] | Available font families for the text tool | | printArea | — | PrintArea \| undefined | undefined | Deprecated — single-decoration fallback, used only when views is empty | | debug | debug | boolean | false | Show the print-area overlay and clamp bounding box on the canvas | | labels | — | Partial<EditorLabels> | {} | Override toolbar strings (see Localizing text) |

Events

| Event | Detail | Description | |---|---|---| | wtpEditorReady | void | Fires when the canvas is initialized | | wtpEditorStateChanged | ArticleEditorState | Fires on any object change (add/move/remove). The active decoration's state.fabricJson is empty — serializing the canvas on every keystroke is too expensive. Call exportState() for the persistable envelope | | wtpEditorViewChanged | { viewId: string; index: number } | Fires when the edited decoration changes | | wtpEditorObjectSelected | { id: string; type: string } | Fires when an object is selected | | wtpEditorObjectDeselected | void | Fires when the selection is cleared |

Methods

| Method | Signature | Description | |---|---|---| | addLogo | (logoData: LogoData) => Promise<string> | Add a logo image, returns its object ID | | addText | (text: string, options?: { fontFamily?, fontSize?, fill? }) => Promise<string> | Add a text object, returns its object ID | | updateText | (id: string, text: string) => Promise<void> | Update the text content of an i-text object by ID | | removeObject | (id: string) => Promise<void> | Remove an object by ID | | resetCanvas | () => Promise<void> | Clear all user objects, keep the canvas instance alive | | exportState | () => Promise<ArticleEditorState> | Export every decoration as a versioned envelope (flushes the active one first) | | loadState | (state: ArticleEditorState \| EditorState) => Promise<void> | Restore an envelope, or a legacy flat state into the default decoration | | setActiveView | (viewId: string) => Promise<void> | Switch decoration, storing the current one | | applyLogoToAllViews | (logoId: string) => Promise<string[]> | Place a logo on every still-empty decoration, returns the ids it touched | | renderMockups | (viewIds?: string[], multiplier?: number) => Promise<Record<string, { dataUrl, width, height }>> | High-resolution mockup per decoration | | exportPdf | (article: Article, config?: Partial<PdfExportConfig>) => Promise<void> | Generate and download the proof PDF (needs jsPDF, see below) | | exportImage | (format?: 'png' \| 'jpeg', quality?: number) => Promise<string> | Export canvas as a data URL (1× resolution) | | exportImageHighRes | (format?, quality?, multiplier?) => Promise<{ dataUrl, width, height }> | High-resolution export for PDF/print (default 3× multiplier) | | exportViewImage | (viewId: string, format?: 'png' \| 'jpeg', quality?: number) => Promise<string> | Render one decoration, active or not, and restore the active one afterwards. Clears the selection unless the decoration is already active | | getObjects | () => Promise<{ id: string; type: string }[]> | List all objects on the canvas |

Multiple decorations per article

An article usually offers several decoration options — front pad print, back pad print, a wrap-around screen print — each with its own product image, print area, print method, size and colour limit. ArticleView[] is that list, and the editor edits all of them in one canvas.

const views = [
  { id: '184932', image: '...', label: 'Beutel', printArea, impMethod: 'Siebdruck', maxColours: 1 },
  { id: '184928', image: '...', label: 'Decke', printArea, impMethod: 'Stick', maxColours: 12, isDefault: true },
];

editor.articleId = article.id;
editor.views = views;

// ... the customer designs one or more decorations ...

const envelope = await editor.exportState();
// { version: 2, articleId, decorations: [{ viewId, status: 'designed' | 'empty', state, previewDataUrl, issues }] }

id must be stable and must not be a position: supplier feeds reorder decorations. In the Connect Shop payload it is the printCodeSKU, which is also the key of the purchasable decoration, so the returned viewId maps straight onto the shop's order lines.

status is mechanical: designed means the decoration's canvas holds at least one logo or text object. Which decorations are actually ordered is a pricing decision and stays in the shop — the editor deliberately has no "selected" flag.

Validation never blocks. DecorationState.issues carries warnings; the shop decides whether one stops checkout. The messages are translatable via labels.issues (see DecorationIssueLabels), and each finding carries a stable code if you would rather render your own text:

| code | Meaning | |---|---| | printAreaOverflow | An element reaches outside the print area | | missingPrintArea | The decoration is designed but has no print area to check against | | sizeOverflow | An element is physically larger than impWidthMm × impHeightMm. Skipped when the catalog declares no mm dimensions | | colourLimit | More text colours than maxColours allows. Skipped for 'full color' | | singleColourPrint | A one-colour method with a logo placed — the colour count of an uploaded logo cannot be measured, so the customer is asked to confirm | | lowDpi | An uploaded raster logo is below 300 DPI. Only reported for logos placed in the current session; the envelope does not persist upload metadata | | productImageUnavailable | The decoration's product image could not be loaded. The editor stays usable on a blank canvas, so this finding is how you learn there is no mockup behind the design |

External dependencies at runtime

Two features expect a global, which keeps them out of the bundle:

| Feature | Global | Script | |---|---|---| | PDF export (exportPdf, exportArticlePdf, exportProductPdf) | window.jspdf | https://unpkg.com/[email protected]/dist/jspdf.umd.min.js | | PDF/AI logo upload | window.pdfjsLib | https://unpkg.com/pdfjs-dist@5/build/pdf.min.mjs |

Both throw a message naming the missing script when they are used without it.

The PDF is a proof, not print data

exportPdf produces one page per distinct logo source (deduplicated, the uploaded original where available) followed by one page per designed decoration with mockup, print-area guide, decoration data and warnings. It is RGB, without bleed or crop marks — a placement reference. The print-ready file is the customer's original upload, which travels through LogoData.sourcePlacedLogo.source into the export envelope.

Migrating from 0.1.x

  • wtp-editor now takes views. productImage and printArea still work for a single decoration but are deprecated.
  • exportState() returns ArticleEditorState instead of the flat EditorState; wtpEditorStateChanged carries the same envelope. loadState() still accepts the old flat shape and loads it into the default decoration.
  • ArticleView.id is required.
  • maxColours is number | 'full color'.
  • Tests run on Vitest (@stencil/vitest) instead of the Stencil Jest runner.

TypeScript Types

All types are exported from the package root:

import type {
  LogoFormat,           // 'png' | 'jpeg' | 'svg' | 'tiff' | 'avif' | 'pdf' | 'ai' | 'unknown'
  LogoMetadata,         // Format, dimensions, DPI, file size, transparency
  LogoValidationConfig, // Validation rules (minDpi, maxFileSize, minWidth, etc.)
  LogoValidationIssue,  // { code, severity, message }
  LogoValidationResult, // { valid, metadata, issues }
  LogoData,             // { dataUrl, previewDataUrl?, source?, metadata }
  LogoSource,           // { dataUrl, mimeType, fileName, fileSize } — the uploaded original
  BgRemovalConfig,      // { tolerance, minEdgeRatio }
  CanvasTransform,      // { x, y, scaleX, scaleY, angle, skewX?, skewY? }
  PlacedLogo,           // { id, dataUrl, previewDataUrl?, source?, transform? }
  PlacedText,           // { id, text, fontFamily, fontSize, fill, transform }
  EditorState,          // { fabricJson, logos, texts, productImage, width, height }
  PrintArea,            // { topLeft, topRight, bottomRight, bottomLeft, bulge? } (0–1 coords)
  RelativePoint,        // { x, y } in 0–1 space
  ArticleView,          // One decoration option: id, image, print area + print-method metadata
  CoordinateImageSize,  // Resolution that pixel print-area coordinates refer to
  MaxColours,           // number | 'full color'
  Article,              // Article with all its decoration options
  DecorationMeta,       // How a decoration prints: method, location, mm size, colour limit
  DecorationState,      // Per-decoration state: viewId, status, state, preview, issues
  ArticleEditorState,   // { version: 2, articleId, decorations }
  LogoUploadLabels,     // Strings used by <wtp-logo-upload>
  EditorLabels,         // Strings used by <wtp-editor>
  DecorationIssueLabels,// Messages for the per-decoration validation findings
} from 'web-to-print';

import {
  DEFAULT_VALIDATION_CONFIG,
  DEFAULT_BG_REMOVAL_CONFIG,
  DEFAULT_LOGO_UPLOAD_LABELS,
  DEFAULT_EDITOR_LABELS,
  DEFAULT_DECORATION_ISSUE_LABELS,
} from 'web-to-print';

Theming

Components expose colors and the font family as CSS custom properties with sensible fallbacks. Override them with a plain CSS rule — no build step or SCSS knowledge required. CSS variables inherit through the Shadow DOM, so the same rule themes wtp-logo-upload (shadow-encapsulated) and the scoped components.

/* Theme all components globally */
:root {
  --wtp-color-primary: #ff6600;
  --wtp-color-primary-hover: #e65500;
  --wtp-color-primary-light: #ffe4d1;
  --wtp-color-primary-fade: rgba(255, 102, 0, 0.2); /* used for focus glow */
  --wtp-font-family: 'Inter', sans-serif;
}

/* Or scope to a single instance */
wtp-editor {
  --wtp-color-border: #aaaaaa;
  --wtp-color-bg-muted: #1e1e1e;
  --wtp-color-text: #ffffff;
}

Available CSS custom properties

| Variable | Default | Description | |---|---|---| | --wtp-color-primary | #2563eb | Primary action color (buttons, focus rings, dashed outlines) | | --wtp-color-primary-hover | #1d4ed8 | Primary hover state | | --wtp-color-primary-light | #dbeafe | Primary tint (drop zone hover/drag-over) | | --wtp-color-primary-fade | rgba(37, 99, 235, 0.2) | Translucent primary (selected-preview glow) | | --wtp-color-secondary | #64748b | Secondary accent (upload icon stroke, preview hover) | | --wtp-color-error | #dc2626 | Error state color | | --wtp-color-error-light | #fee2e2 | Error background tint | | --wtp-color-success | #16a34a | Success state color | | --wtp-color-success-light | #dcfce7 | Success background tint | | --wtp-color-warning | #d97706 | Warning state color | | --wtp-color-warning-light | #fef3c7 | Warning background tint | | --wtp-color-text | #1e293b | Primary text color | | --wtp-color-text-muted | #64748b | Muted/secondary text color | | --wtp-color-border | #e2e8f0 | Border color | | --wtp-color-bg | #ffffff | Background color | | --wtp-color-bg-muted | #f8fafc | Muted background (toolbar, drop zone) | | --wtp-font-family | system stack | UI font family |

Spacing and border-radius remain build-time SCSS tokens ($wtp-spacing-*, $wtp-radius-*) — they're not exposed as CSS variables. Fork the package if you need to change them.

::part() styling (wtp-logo-upload)

Because wtp-logo-upload uses Shadow DOM, internal elements aren't reachable with normal selectors. Use the ::part() pseudo-element to target specific elements:

wtp-logo-upload::part(url-submit-btn) {
  background: #16a34a;
  border-color: #16a34a;
}

wtp-logo-upload::part(upload-zone drag-over) {
  border-color: red;
  background: #ffe5e5;
}

wtp-logo-upload::part(preview-item selected) {
  outline: 3px solid gold;
}

| Part name | Element | |---|---| | root | Outer wrapper | | url-input | URL <input type="url"> | | url-submit-btn | "Fetch" button | | url-error | URL error message paragraph | | divider | "or" divider line | | upload-zone | Drag-and-drop zone (also drag-over and disabled modifier parts) | | prompt-text / prompt-hint | Default prompt text and hint inside the drop zone | | rejections / rejection-item | Validation failure container and items | | pending-choices / choice-card / choice-option | Background-removal choice cards | | previews / preview-item | Preview gallery and items (selected items also get selected) | | preview-select | The button inside a preview card that selects it | | remove-btn | Per-preview remove button |

Localizing text (labels prop)

wtp-logo-upload and wtp-editor accept a labels prop with partial overrides — supply only the keys you want to change; missing keys fall back to the English defaults.

<wtp-logo-upload id="uploader"></wtp-logo-upload>
<script>
  document.getElementById('uploader').labels = {
    dropPromptText: 'Logo hierher ziehen oder klicken',
    dropPromptHint: 'PNG, JPEG, SVG, TIFF oder AVIF',
    dividerText: 'oder',
    urlSubmit: 'Laden',
    urlPlaceholder: 'https://beispiel.de/logo.png',
    urlErrorEmpty: 'Bitte eine URL eingeben',
    urlErrorInvalid: 'Ungültiges URL-Format',
    urlErrorProtocol: 'Nur HTTPS-URLs werden unterstützt',
    urlErrorNetwork: 'Bild konnte nicht geladen werden (CORS).',
    bgRemovalUseOriginal: 'Original verwenden',
    bgRemovalUseRemoved: 'Ohne Hintergrund',
    bgRemovalProcessing: 'Hintergrund wird entfernt...',
    bgRemovalFailed: 'Fehlgeschlagen',
    uploadAriaLabel: 'Logo-Datei hochladen',
    removeAriaLabel: (name) => `${name} entfernen`,
    rejectionDpiUnit: 'DPI',
  };
</script>
document.querySelector('wtp-editor').labels = {
  addTextButton: 'Text hinzufügen',
  addTextTooltip: 'Text hinzufügen',
  fontSelectTooltip: 'Schriftart',
  colorPickerTooltip: 'Textfarbe',
  deleteButtonTooltip: 'Auswahl löschen',
  defaultText: 'Neuer Text',
};

Import the type and the defaults if you want to start from a complete object:

import {
  LogoUploadLabels,
  EditorLabels,
  DEFAULT_LOGO_UPLOAD_LABELS,
  DEFAULT_EDITOR_LABELS,
  DEFAULT_DECORATION_ISSUE_LABELS,
} from 'web-to-print';

const myLabels: LogoUploadLabels = {
  ...DEFAULT_LOGO_UPLOAD_LABELS,
  dividerText: 'oder',
};

LogoUploadLabels keys

urlPlaceholder, urlSubmit, dividerText, dropPromptText, dropPromptHint, uploadAriaLabel, removeAriaLabel(fileName), urlErrorEmpty, urlErrorInvalid, urlErrorProtocol, urlErrorHttp(status, statusText), urlErrorNetwork, urlErrorFetch, bgRemovalProcessing, bgRemovalUseOriginal, bgRemovalUseRemoved, bgRemovalFailed, rejectionDpiUnit.

EditorLabels keys

addTextButton, addTextTooltip, fontSelectTooltip, colorPickerTooltip, deleteButtonTooltip, defaultText.

Development

# Install dependencies
npm install

# Dev server with hot reload
npm start

# Build for production
npm run build

# Run all tests (spec + e2e)
npm test

# Run only unit tests
npm run test.spec

# Run only e2e tests
npm run test.e2e

# Run tests in watch mode
npm run test.watch

# Run a single test file
npx stencil test --spec -- src/utils/format-detection.spec.ts

# Lint
npm run lint

Release

Releases are published to npm via a GitHub Action (.github/workflows/release.yml). Two flows are supported:

Option A — local version bump (recommended)

npm run release:patch   # 0.1.0 → 0.1.1
npm run release:minor   # 0.1.0 → 0.2.0
npm run release:major   # 0.1.0 → 1.0.0

Each script bumps package.json, creates a v<version> git tag, and pushes both. The GitHub Action then runs tests, builds, publishes to npm (with provenance attestation), and creates a GitHub Release with auto-generated notes.

Option B — manual via GitHub UI

Trigger the Release workflow from the Actions tab with a bump (patch/minor/major) or an explicit version like 1.2.3. The workflow performs the bump, commit, tag, and push, then publishes.

Prerequisites

  1. NPM_TOKEN secret on the repo (Settings → Secrets and variables → Actions). Create an automation token on npmjs.com that allows publish.
  2. The package name in package.json must be available on npm (or use a scoped name like @your-org/web-to-print). Provenance requires the workflow to run on a public repo, so make sure the repo is public when publishing.
  3. The workflow skips publish if the same name@version already exists on npm — no need to manually guard against accidental re-runs.

Project Structure

src/
  components/
    wtp-editor/            Interactive canvas editor
    wtp-logo-renderer/     Static logo-on-product renderer (HTML/img-based)
    wtp-logo-upload/       Logo upload with validation
    wtp-print-area-editor/ Interactive print-area definition tool (4-corner quad + bulge)
  styles/
    _variables.scss        Design tokens (colors and font-family exposed as CSS vars)
    _mixins.scss           SCSS mixins
    _reset.scss            CSS reset
  types/
    logo.ts                Logo-related interfaces
    editor.ts              Editor/canvas interfaces
    labels.ts              User-facing string interfaces and English defaults
    index.ts               Re-exports
  utils/
    background-removal.ts  Color-based flood-fill background removal
    canvas-helpers.ts      Fabric.js canvas utilities + CORS-safe image loader
    format-detection.ts    Magic-byte file format detection
    logo-validation.ts     DPI, dimension, and format validation
    image-preview.ts       Downscaled preview generation
    html-render-helpers.ts <img>-based layout/export helpers for the renderer
    pdf-export.ts          Multi-page PDF export with print-area metadata
  index.ts                 Public API exports
  index.html               Dev server demo page
scripts/
  image-proxy.mjs          Local dev-only CORS proxy for cross-origin product images
stencil.config.ts          Stencil build configuration

The dev script (npm start) automatically launches scripts/image-proxy.mjs on port 3001. The canvas helper tries direct CORS first, then this proxy as a fallback, before loading the image tainted (which would block export).

Whichever route works is remembered per URL, so switching between decorations does not repeat a failed CORS request every time. The tainted fallback is remembered only for a minute: unlike the other two it never fails, so nothing would ever retire it, and a session open while the shop fixes its CORS headers would stay unable to export.

Key Dependencies

| Package | Purpose | |---|---| | Fabric.js v7 | Canvas rendering, object manipulation, JSON serialization | | ExifReader | DPI and metadata extraction from raster images | | — | Background removal uses a built-in color-based flood-fill (zero dependencies) | | @stencil/core v4 | Web component compiler | | @stencil/sass | SCSS support |

License

MIT