First commit.

Signed-off-by: Chen Xiao <abigwc@gmail.com>
This commit is contained in:
Chen Xiao
2026-05-08 14:43:16 +08:00
commit 0b64e2de94
10989 changed files with 2253791 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
import { PatternFiller, RenderHelper } from './filler-interface';
import { ResolvedOptions, OpSet } from '../core';
import { Point } from '../geometry';
export declare class DashedFiller implements PatternFiller {
private helper;
constructor(helper: RenderHelper);
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
private dashedLine;
}
+36
View File
@@ -0,0 +1,36 @@
import { lineLength } from '../geometry';
import { polygonHachureLines } from './scan-line-hachure';
export class DashedFiller {
constructor(helper) {
this.helper = helper;
}
fillPolygons(polygonList, o) {
const lines = polygonHachureLines(polygonList, o);
return { type: 'fillSketch', ops: this.dashedLine(lines, o) };
}
dashedLine(lines, o) {
const offset = o.dashOffset < 0 ? (o.hachureGap < 0 ? (o.strokeWidth * 4) : o.hachureGap) : o.dashOffset;
const gap = o.dashGap < 0 ? (o.hachureGap < 0 ? (o.strokeWidth * 4) : o.hachureGap) : o.dashGap;
const ops = [];
lines.forEach((line) => {
const length = lineLength(line);
const count = Math.floor(length / (offset + gap));
const startOffset = (length + gap - (count * (offset + gap))) / 2;
let p1 = line[0];
let p2 = line[1];
if (p1[0] > p2[0]) {
p1 = line[1];
p2 = line[0];
}
const alpha = Math.atan((p2[1] - p1[1]) / (p2[0] - p1[0]));
for (let i = 0; i < count; i++) {
const lstart = i * (offset + gap);
const lend = lstart + offset;
const start = [p1[0] + (lstart * Math.cos(alpha)) + (startOffset * Math.cos(alpha)), p1[1] + lstart * Math.sin(alpha) + (startOffset * Math.sin(alpha))];
const end = [p1[0] + (lend * Math.cos(alpha)) + (startOffset * Math.cos(alpha)), p1[1] + (lend * Math.sin(alpha)) + (startOffset * Math.sin(alpha))];
ops.push(...this.helper.doubleLineOps(start[0], start[1], end[0], end[1], o));
}
});
return ops;
}
}
+9
View File
@@ -0,0 +1,9 @@
import { PatternFiller, RenderHelper } from './filler-interface';
import { ResolvedOptions, OpSet } from '../core';
import { Point } from '../geometry';
export declare class DotFiller implements PatternFiller {
private helper;
constructor(helper: RenderHelper);
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
private dotsOnLines;
}
+41
View File
@@ -0,0 +1,41 @@
import { lineLength } from '../geometry';
import { polygonHachureLines } from './scan-line-hachure';
export class DotFiller {
constructor(helper) {
this.helper = helper;
}
fillPolygons(polygonList, o) {
o = Object.assign({}, o, { hachureAngle: 0 });
const lines = polygonHachureLines(polygonList, o);
return this.dotsOnLines(lines, o);
}
dotsOnLines(lines, o) {
const ops = [];
let gap = o.hachureGap;
if (gap < 0) {
gap = o.strokeWidth * 4;
}
gap = Math.max(gap, 0.1);
let fweight = o.fillWeight;
if (fweight < 0) {
fweight = o.strokeWidth / 2;
}
const ro = gap / 4;
for (const line of lines) {
const length = lineLength(line);
const dl = length / gap;
const count = Math.ceil(dl) - 1;
const offset = length - (count * gap);
const x = ((line[0][0] + line[1][0]) / 2) - (gap / 4);
const minY = Math.min(line[0][1], line[1][1]);
for (let i = 0; i < count; i++) {
const y = minY + offset + (i * gap);
const cx = (x - ro) + Math.random() * 2 * ro;
const cy = (y - ro) + Math.random() * 2 * ro;
const el = this.helper.ellipse(cx, cy, fweight, fweight, o);
ops.push(...el.ops);
}
}
return { type: 'fillSketch', ops };
}
}
+11
View File
@@ -0,0 +1,11 @@
import { ResolvedOptions, OpSet, Op } from '../core';
import { Point } from '../geometry';
export interface PatternFiller {
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
}
export interface RenderHelper {
randOffset(x: number, o: ResolvedOptions): number;
randOffsetWithRange(min: number, max: number, o: ResolvedOptions): number;
ellipse(x: number, y: number, width: number, height: number, o: ResolvedOptions): OpSet;
doubleLineOps(x1: number, y1: number, x2: number, y2: number, o: ResolvedOptions): Op[];
}
+1
View File
@@ -0,0 +1 @@
export {};
+3
View File
@@ -0,0 +1,3 @@
import { ResolvedOptions } from '../core';
import { PatternFiller, RenderHelper } from './filler-interface';
export declare function getFiller(o: ResolvedOptions, helper: RenderHelper): PatternFiller;
+47
View File
@@ -0,0 +1,47 @@
import { HachureFiller } from './hachure-filler';
import { ZigZagFiller } from './zigzag-filler';
import { HatchFiller } from './hatch-filler';
import { DotFiller } from './dot-filler';
import { DashedFiller } from './dashed-filler';
import { ZigZagLineFiller } from './zigzag-line-filler';
const fillers = {};
export function getFiller(o, helper) {
let fillerName = o.fillStyle || 'hachure';
if (!fillers[fillerName]) {
switch (fillerName) {
case 'zigzag':
if (!fillers[fillerName]) {
fillers[fillerName] = new ZigZagFiller(helper);
}
break;
case 'cross-hatch':
if (!fillers[fillerName]) {
fillers[fillerName] = new HatchFiller(helper);
}
break;
case 'dots':
if (!fillers[fillerName]) {
fillers[fillerName] = new DotFiller(helper);
}
break;
case 'dashed':
if (!fillers[fillerName]) {
fillers[fillerName] = new DashedFiller(helper);
}
break;
case 'zigzag-line':
if (!fillers[fillerName]) {
fillers[fillerName] = new ZigZagLineFiller(helper);
}
break;
case 'hachure':
default:
fillerName = 'hachure';
if (!fillers[fillerName]) {
fillers[fillerName] = new HachureFiller(helper);
}
break;
}
}
return fillers[fillerName];
}
+10
View File
@@ -0,0 +1,10 @@
import { PatternFiller, RenderHelper } from './filler-interface';
import { ResolvedOptions, OpSet, Op } from '../core';
import { Point, Line } from '../geometry';
export declare class HachureFiller implements PatternFiller {
private helper;
constructor(helper: RenderHelper);
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
protected _fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
protected renderLines(lines: Line[], o: ResolvedOptions): Op[];
}
+21
View File
@@ -0,0 +1,21 @@
import { polygonHachureLines } from './scan-line-hachure';
export class HachureFiller {
constructor(helper) {
this.helper = helper;
}
fillPolygons(polygonList, o) {
return this._fillPolygons(polygonList, o);
}
_fillPolygons(polygonList, o) {
const lines = polygonHachureLines(polygonList, o);
const ops = this.renderLines(lines, o);
return { type: 'fillSketch', ops };
}
renderLines(lines, o) {
const ops = [];
for (const line of lines) {
ops.push(...this.helper.doubleLineOps(line[0][0], line[0][1], line[1][0], line[1][1], o));
}
return ops;
}
}
+6
View File
@@ -0,0 +1,6 @@
import { HachureFiller } from './hachure-filler';
import { ResolvedOptions, OpSet } from '../core';
import { Point } from '../geometry';
export declare class HatchFiller extends HachureFiller {
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
}
+10
View File
@@ -0,0 +1,10 @@
import { HachureFiller } from './hachure-filler';
export class HatchFiller extends HachureFiller {
fillPolygons(polygonList, o) {
const set = this._fillPolygons(polygonList, o);
const o2 = Object.assign({}, o, { hachureAngle: o.hachureAngle + 90 });
const set2 = this._fillPolygons(polygonList, o2);
set.ops = set.ops.concat(set2.ops);
return set;
}
}
+3
View File
@@ -0,0 +1,3 @@
import { Point, Line } from '../geometry';
import { ResolvedOptions } from '../core';
export declare function polygonHachureLines(polygonList: Point[][], o: ResolvedOptions): Line[];
+17
View File
@@ -0,0 +1,17 @@
import { hachureLines } from 'hachure-fill';
export function polygonHachureLines(polygonList, o) {
var _a;
const angle = o.hachureAngle + 90;
let gap = o.hachureGap;
if (gap < 0) {
gap = o.strokeWidth * 4;
}
gap = Math.round(Math.max(gap, 0.1));
let skipOffset = 1;
if (o.roughness >= 1) {
if ((((_a = o.randomizer) === null || _a === void 0 ? void 0 : _a.next()) || Math.random()) > 0.7) {
skipOffset = gap;
}
}
return hachureLines(polygonList, gap, angle, skipOffset || 1);
}
+6
View File
@@ -0,0 +1,6 @@
import { HachureFiller } from './hachure-filler';
import { ResolvedOptions, OpSet } from '../core';
import { Point } from '../geometry';
export declare class ZigZagFiller extends HachureFiller {
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
}
+31
View File
@@ -0,0 +1,31 @@
import { HachureFiller } from './hachure-filler';
import { polygonHachureLines } from './scan-line-hachure';
import { lineLength } from '../geometry';
export class ZigZagFiller extends HachureFiller {
fillPolygons(polygonList, o) {
let gap = o.hachureGap;
if (gap < 0) {
gap = o.strokeWidth * 4;
}
gap = Math.max(gap, 0.1);
const o2 = Object.assign({}, o, { hachureGap: gap });
const lines = polygonHachureLines(polygonList, o2);
const zigZagAngle = (Math.PI / 180) * o.hachureAngle;
const zigzagLines = [];
const dgx = gap * 0.5 * Math.cos(zigZagAngle);
const dgy = gap * 0.5 * Math.sin(zigZagAngle);
for (const [p1, p2] of lines) {
if (lineLength([p1, p2])) {
zigzagLines.push([
[p1[0] - dgx, p1[1] + dgy],
[...p2],
], [
[p1[0] + dgx, p1[1] - dgy],
[...p2],
]);
}
}
const ops = this.renderLines(zigzagLines, o);
return { type: 'fillSketch', ops };
}
}
+9
View File
@@ -0,0 +1,9 @@
import { PatternFiller, RenderHelper } from './filler-interface';
import { ResolvedOptions, OpSet } from '../core';
import { Point } from '../geometry';
export declare class ZigZagLineFiller implements PatternFiller {
private helper;
constructor(helper: RenderHelper);
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
private zigzagLines;
}
+38
View File
@@ -0,0 +1,38 @@
import { lineLength } from '../geometry';
import { polygonHachureLines } from './scan-line-hachure';
export class ZigZagLineFiller {
constructor(helper) {
this.helper = helper;
}
fillPolygons(polygonList, o) {
const gap = o.hachureGap < 0 ? (o.strokeWidth * 4) : o.hachureGap;
const zo = o.zigzagOffset < 0 ? gap : o.zigzagOffset;
o = Object.assign({}, o, { hachureGap: gap + zo });
const lines = polygonHachureLines(polygonList, o);
return { type: 'fillSketch', ops: this.zigzagLines(lines, zo, o) };
}
zigzagLines(lines, zo, o) {
const ops = [];
lines.forEach((line) => {
const length = lineLength(line);
const count = Math.round(length / (2 * zo));
let p1 = line[0];
let p2 = line[1];
if (p1[0] > p2[0]) {
p1 = line[1];
p2 = line[0];
}
const alpha = Math.atan((p2[1] - p1[1]) / (p2[0] - p1[0]));
for (let i = 0; i < count; i++) {
const lstart = i * 2 * zo;
const lend = (i + 1) * 2 * zo;
const dz = Math.sqrt(2 * Math.pow(zo, 2));
const start = [p1[0] + (lstart * Math.cos(alpha)), p1[1] + lstart * Math.sin(alpha)];
const end = [p1[0] + (lend * Math.cos(alpha)), p1[1] + (lend * Math.sin(alpha))];
const middle = [start[0] + dz * Math.cos(alpha + Math.PI / 4), start[1] + dz * Math.sin(alpha + Math.PI / 4)];
ops.push(...this.helper.doubleLineOps(start[0], start[1], middle[0], middle[1], o), ...this.helper.doubleLineOps(middle[0], middle[1], end[0], end[1], o));
}
});
return ops;
}
}