2026-10-06 15:58:02 +02:00
|
|
|
// @ts-check
|
|
|
|
|
/**
|
|
|
|
|
* The shape of a stroke, shared by everything that draws one: the screen
|
|
|
|
|
* (renderer.js) and the page file (page_file.js).
|
|
|
|
|
*
|
|
|
|
|
* A stroke is a chain of *parts*, one per point. The part for a point runs
|
|
|
|
|
* from the midpoint before it to the midpoint after it, curving through the
|
|
|
|
|
* point, with a width taken from the pen pressure at that point. At the
|
|
|
|
|
* ends of the stroke the part runs to the end point itself.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { POINT_SIZE, pointCount } from './page_model.js';
|
|
|
|
|
|
|
|
|
|
/** @typedef {import('./page_model.js').Stroke} Stroke */
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @typedef {object} Part
|
|
|
|
|
* @property {number} fromX
|
|
|
|
|
* @property {number} fromY
|
|
|
|
|
* @property {number} x The point itself, the control point of the curve.
|
|
|
|
|
* @property {number} y
|
|
|
|
|
* @property {number} toX
|
|
|
|
|
* @property {number} toY
|
|
|
|
|
* @property {number} width
|
|
|
|
|
* @property {boolean} dot The stroke is a single point: a filled circle
|
|
|
|
|
* of diameter `width` at (x, y).
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The drawn width of a stroke at one of its points. The stroke's nominal
|
|
|
|
|
* width is drawn at pressure 0.4, about the pressure of ordinary writing
|
|
|
|
|
* (Andy's averaged 0.34 to 0.45 on a Cintiq 16 and rarely passed 0.8). A
|
|
|
|
|
* firm press, 0.75, is about 2.6 times as wide.
|
|
|
|
|
* @param {Stroke} stroke
|
|
|
|
|
* @param {number} index Point index.
|
|
|
|
|
*/
|
|
|
|
|
export function widthAt(stroke, index) {
|
|
|
|
|
const pressure = stroke.points[index * POINT_SIZE + 2];
|
|
|
|
|
return stroke.width * (0.36 + 4 * pressure * pressure);
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-14 20:38:29 +02:00
|
|
|
/**
|
|
|
|
|
* The rectangle a stroke's ink covers: around its points, each taken
|
|
|
|
|
* out by half the line's width at that point (round caps and joins).
|
|
|
|
|
* @param {Stroke} stroke
|
|
|
|
|
* @returns {{left: number, top: number, width: number, height: number} | null}
|
|
|
|
|
*/
|
|
|
|
|
export function inkExtent(stroke) {
|
|
|
|
|
const p = stroke.points;
|
|
|
|
|
if (p.length < POINT_SIZE) return null;
|
|
|
|
|
let left = Infinity;
|
|
|
|
|
let top = Infinity;
|
|
|
|
|
let right = -Infinity;
|
|
|
|
|
let bottom = -Infinity;
|
|
|
|
|
for (let i = 0, k = 0; i < p.length; i += POINT_SIZE, k++) {
|
|
|
|
|
const half = widthAt(stroke, k) / 2;
|
|
|
|
|
left = Math.min(left, p[i] - half);
|
|
|
|
|
right = Math.max(right, p[i] + half);
|
|
|
|
|
top = Math.min(top, p[i + 1] - half);
|
|
|
|
|
bottom = Math.max(bottom, p[i + 1] + half);
|
|
|
|
|
}
|
|
|
|
|
return { left, top, width: right - left, height: bottom - top };
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-06 15:58:02 +02:00
|
|
|
/**
|
|
|
|
|
* The part of a stroke that belongs to point `k`.
|
|
|
|
|
* @param {Stroke} stroke
|
|
|
|
|
* @param {number} k
|
|
|
|
|
* @returns {Part}
|
|
|
|
|
*/
|
|
|
|
|
export function partAt(stroke, k) {
|
|
|
|
|
const p = stroke.points;
|
|
|
|
|
const last = pointCount(stroke) - 1;
|
|
|
|
|
const x = p[k * POINT_SIZE];
|
|
|
|
|
const y = p[k * POINT_SIZE + 1];
|
|
|
|
|
return {
|
|
|
|
|
fromX: k === 0 ? x : (p[(k - 1) * POINT_SIZE] + x) / 2,
|
|
|
|
|
fromY: k === 0 ? y : (p[(k - 1) * POINT_SIZE + 1] + y) / 2,
|
|
|
|
|
x,
|
|
|
|
|
y,
|
|
|
|
|
toX: k === last ? x : (x + p[(k + 1) * POINT_SIZE]) / 2,
|
|
|
|
|
toY: k === last ? y : (y + p[(k + 1) * POINT_SIZE + 1]) / 2,
|
|
|
|
|
width: widthAt(stroke, k),
|
|
|
|
|
dot: last === 0,
|
|
|
|
|
};
|
|
|
|
|
}
|