mirror of
https://github.com/slidevjs/rough-notation.git
synced 2026-01-16 18:27:56 +01:00
initial code
This commit is contained in:
149
src/render.ts
Normal file
149
src/render.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { Rect, RoughAnnotationConfig, SVG_NS } from './model.js';
|
||||
import { ResolvedOptions, OpSet } from 'roughjs/bin/core';
|
||||
import { line, rectangle, ellipse } from 'roughjs/bin/renderer';
|
||||
|
||||
const defaultOptions: ResolvedOptions = {
|
||||
maxRandomnessOffset: 2,
|
||||
roughness: 1.5,
|
||||
bowing: 1,
|
||||
stroke: '#000',
|
||||
strokeWidth: 1.5,
|
||||
curveTightness: 0,
|
||||
curveFitting: 0.95,
|
||||
curveStepCount: 9,
|
||||
fillStyle: 'hachure',
|
||||
fillWeight: -1,
|
||||
hachureAngle: -41,
|
||||
hachureGap: -1,
|
||||
dashOffset: -1,
|
||||
dashGap: -1,
|
||||
zigzagOffset: -1,
|
||||
seed: 0,
|
||||
combineNestedSvgPaths: false,
|
||||
disableMultiStroke: false,
|
||||
disableMultiStrokeFill: false
|
||||
};
|
||||
const singleStrokeOptions = JSON.parse(JSON.stringify(defaultOptions));
|
||||
singleStrokeOptions.disableMultiStroke = true;
|
||||
const highlightOptions = JSON.parse(JSON.stringify(defaultOptions));
|
||||
highlightOptions.roughness = 3;
|
||||
|
||||
export function renderAnnotation(svg: SVGSVGElement, rect: Rect, config: RoughAnnotationConfig) {
|
||||
let ops: OpSet | null = null;
|
||||
let strokeWidth = config.strokeWidth || 2;
|
||||
const padding = (config.padding === 0) ? 0 : (config.padding || 5);
|
||||
const animate = (config.animate === undefined) ? true : (!!config.animate);
|
||||
|
||||
switch (config.type) {
|
||||
case 'underline': {
|
||||
const y = rect.y + rect.h + padding;
|
||||
ops = line(rect.x, y, rect.x + rect.w, y, defaultOptions);
|
||||
break;
|
||||
}
|
||||
case 'strike-through': {
|
||||
const y = rect.y + (rect.h / 2);
|
||||
ops = line(rect.x, y, rect.x + rect.w, y, defaultOptions);
|
||||
break;
|
||||
}
|
||||
case 'box': {
|
||||
const x = rect.x - padding;
|
||||
const y = rect.y - padding;
|
||||
const width = rect.w + (2 * padding);
|
||||
const height = rect.h + (2 * padding);
|
||||
ops = rectangle(x, y, width, height, singleStrokeOptions);
|
||||
const ops2 = rectangle(x, y, width, height, singleStrokeOptions);
|
||||
ops.ops = [...ops.ops, ...ops2.ops];
|
||||
break;
|
||||
}
|
||||
case 'crossed-off': {
|
||||
const x = rect.x;
|
||||
const y = rect.y;
|
||||
const x2 = x + rect.w;
|
||||
const y2 = y + rect.h;
|
||||
ops = line(x, y, x2, y2, defaultOptions);
|
||||
const ops2 = line(x2, y, x, y2, defaultOptions);
|
||||
ops.ops = [...ops.ops, ...ops2.ops];
|
||||
break;
|
||||
}
|
||||
case 'circle': {
|
||||
const p2 = padding * 2;
|
||||
const width = rect.w + (2 * p2);
|
||||
const height = rect.h + (2 * p2);
|
||||
const x = rect.x - p2 + (width / 2);
|
||||
const y = rect.y - p2 + (height / 2);
|
||||
ops = ellipse(x, y, width, height, defaultOptions);
|
||||
break;
|
||||
}
|
||||
case 'highlight': {
|
||||
strokeWidth = rect.h * 0.95;
|
||||
const y = rect.y + (rect.h / 2);
|
||||
ops = line(rect.x, y, rect.x + rect.w, y, highlightOptions);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ops) {
|
||||
const pathStrings = opsToPath(ops);
|
||||
const lengths: number[] = [];
|
||||
const pathElements: SVGPathElement[] = [];
|
||||
let totalLength = 0;
|
||||
const totalDuration = config.animationDuration === 0 ? 0 : (config.animationDuration || 500);
|
||||
const initialDelay = config.animationDelay === 0 ? 0 : (config.animationDelay || 0);
|
||||
|
||||
for (const d of pathStrings) {
|
||||
const path = document.createElementNS(SVG_NS, 'path');
|
||||
path.setAttribute('d', d);
|
||||
path.setAttribute('fill', 'none');
|
||||
path.setAttribute('stroke', config.color || 'currentColor');
|
||||
path.setAttribute('stroke-width', `${strokeWidth}`);
|
||||
if (animate) {
|
||||
const length = path.getTotalLength();
|
||||
lengths.push(length);
|
||||
totalLength += length;
|
||||
}
|
||||
svg.appendChild(path);
|
||||
pathElements.push(path);
|
||||
}
|
||||
|
||||
if (animate) {
|
||||
let durationOffset = 0;
|
||||
for (let i = 0; i < pathElements.length; i++) {
|
||||
const path = pathElements[i];
|
||||
const length = lengths[i];
|
||||
const duration = totalLength ? (totalDuration * (length / totalLength)) : 0;
|
||||
const delay = initialDelay + durationOffset;
|
||||
const style = path.style;
|
||||
style.strokeDashoffset = `${length}`;
|
||||
style.strokeDasharray = `${length}`;
|
||||
style.animation = `rough-notation-dash ${duration}ms ease-out ${delay}ms forwards`;
|
||||
durationOffset += duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function opsToPath(drawing: OpSet): string[] {
|
||||
const paths: string[] = [];
|
||||
let path = '';
|
||||
for (const item of drawing.ops) {
|
||||
const data = item.data;
|
||||
switch (item.op) {
|
||||
case 'move':
|
||||
if (path.trim()) {
|
||||
paths.push(path.trim());
|
||||
}
|
||||
path = `M${data[0]} ${data[1]} `;
|
||||
break;
|
||||
case 'bcurveTo':
|
||||
path += `C${data[0]} ${data[1]}, ${data[2]} ${data[3]}, ${data[4]} ${data[5]} `;
|
||||
break;
|
||||
case 'lineTo':
|
||||
path += `L${data[0]} ${data[1]} `;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (path.trim()) {
|
||||
paths.push(path.trim());
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
Reference in New Issue
Block a user