Skip to content

Commit

Permalink
initial code
Browse files Browse the repository at this point in the history
  • Loading branch information
pshihn committed May 27, 2020
1 parent a2e4cd3 commit 6cec7bf
Show file tree
Hide file tree
Showing 9 changed files with 535 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
bin
dist
lib
demo
43 changes: 43 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "rough-notation",
"version": "0.0.1",
"description": "Annotate html",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/pshihn/rough-notation.git"
},
"keywords": [
"annotate",
"rough",
"sketchy"
],
"author": "Preet Shihn",
"license": "MIT",
"bugs": {
"url": "https://github.com/pshihn/rough-notation/issues"
},
"homepage": "https://github.com/pshihn/rough-notation#readme",
"devDependencies": {
"typescript": "^3.9.3"
},
"dependencies": {
"roughjs": "^4.3.1"
}
}
14 changes: 14 additions & 0 deletions src/keyframes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export function ensureKeyframes() {
if (!(window as any).__rough_notation_keyframe_styles) {
const style = (window as any).__rough_notation_keyframe_styles = document.createElement('style');
style.textContent = `
@keyframes rough-notation-dash {
to {
stroke-dashoffset: 0;
}
}
`;
console.log('keyframe added');
document.head.appendChild(style);
}
}
27 changes: 27 additions & 0 deletions src/model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export const SVG_NS = 'http://www.w3.org/2000/svg';

export interface Rect {
x: number;
y: number;
w: number;
h: number;
}

export type RoughAnnotationType = 'underline' | 'box' | 'circle' | 'highlight' | 'strike-through' | 'crossed-off';

export interface RoughAnnotationConfig {
type: RoughAnnotationType;
animate?: boolean; // defaults to true
animationDuration?: number; // defaulst to 1000ms
animationDelay?: number; // default = 0
color?: string; // defaults to currentColor
strokeWidth?: number; // default based on type
padding?: number; // defaults to 5px
}

export interface RoughAnnotation {
isShowing(): boolean;
show(): void;
hide(): void;
remove(): void;
}
149 changes: 149 additions & 0 deletions src/render.ts
Original file line number Diff line number Diff line change
@@ -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;
}
Loading

0 comments on commit 6cec7bf

Please sign in to comment.