Buckets:
| import { Color } from '../../math/Color.js'; | |
| import { Path } from './Path.js'; | |
| import { Shape } from './Shape.js'; | |
| import { ShapeUtils } from '../ShapeUtils.js'; | |
| /** | |
| * This class is used to convert a series of paths to an array of | |
| * shapes. It is specifically used in context of fonts and SVG. | |
| */ | |
| class ShapePath { | |
| /** | |
| * Constructs a new shape path. | |
| */ | |
| constructor() { | |
| this.type = 'ShapePath'; | |
| /** | |
| * The color of the shape. | |
| * | |
| * @type {Color} | |
| */ | |
| this.color = new Color(); | |
| /** | |
| * The paths that have been generated for this shape. | |
| * | |
| * @type {Array<Path>} | |
| * @default null | |
| */ | |
| this.subPaths = []; | |
| /** | |
| * The current path that is being generated. | |
| * | |
| * @type {?Path} | |
| * @default null | |
| */ | |
| this.currentPath = null; | |
| } | |
| /** | |
| * Creates a new path and moves it current point to the given one. | |
| * | |
| * @param {number} x - The x coordinate. | |
| * @param {number} y - The y coordinate. | |
| * @return {ShapePath} A reference to this shape path. | |
| */ | |
| moveTo( x, y ) { | |
| this.currentPath = new Path(); | |
| this.subPaths.push( this.currentPath ); | |
| this.currentPath.moveTo( x, y ); | |
| return this; | |
| } | |
| /** | |
| * Adds an instance of {@link LineCurve} to the path by connecting | |
| * the current point with the given one. | |
| * | |
| * @param {number} x - The x coordinate of the end point. | |
| * @param {number} y - The y coordinate of the end point. | |
| * @return {ShapePath} A reference to this shape path. | |
| */ | |
| lineTo( x, y ) { | |
| this.currentPath.lineTo( x, y ); | |
| return this; | |
| } | |
| /** | |
| * Adds an instance of {@link QuadraticBezierCurve} to the path by connecting | |
| * the current point with the given one. | |
| * | |
| * @param {number} aCPx - The x coordinate of the control point. | |
| * @param {number} aCPy - The y coordinate of the control point. | |
| * @param {number} aX - The x coordinate of the end point. | |
| * @param {number} aY - The y coordinate of the end point. | |
| * @return {ShapePath} A reference to this shape path. | |
| */ | |
| quadraticCurveTo( aCPx, aCPy, aX, aY ) { | |
| this.currentPath.quadraticCurveTo( aCPx, aCPy, aX, aY ); | |
| return this; | |
| } | |
| /** | |
| * Adds an instance of {@link CubicBezierCurve} to the path by connecting | |
| * the current point with the given one. | |
| * | |
| * @param {number} aCP1x - The x coordinate of the first control point. | |
| * @param {number} aCP1y - The y coordinate of the first control point. | |
| * @param {number} aCP2x - The x coordinate of the second control point. | |
| * @param {number} aCP2y - The y coordinate of the second control point. | |
| * @param {number} aX - The x coordinate of the end point. | |
| * @param {number} aY - The y coordinate of the end point. | |
| * @return {ShapePath} A reference to this shape path. | |
| */ | |
| bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) { | |
| this.currentPath.bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ); | |
| return this; | |
| } | |
| /** | |
| * Adds an instance of {@link SplineCurve} to the path by connecting | |
| * the current point with the given list of points. | |
| * | |
| * @param {Array<Vector2>} pts - An array of points in 2D space. | |
| * @return {ShapePath} A reference to this shape path. | |
| */ | |
| splineThru( pts ) { | |
| this.currentPath.splineThru( pts ); | |
| return this; | |
| } | |
| /** | |
| * Converts the paths into an array of shapes. | |
| * | |
| * @param {boolean} isCCW - By default solid shapes are defined clockwise (CW) and holes are defined counterclockwise (CCW). | |
| * If this flag is set to `true`, then those are flipped. | |
| * @return {Array<Shape>} An array of shapes. | |
| */ | |
| toShapes( isCCW ) { | |
| function toShapesNoHoles( inSubpaths ) { | |
| const shapes = []; | |
| for ( let i = 0, l = inSubpaths.length; i < l; i ++ ) { | |
| const tmpPath = inSubpaths[ i ]; | |
| const tmpShape = new Shape(); | |
| tmpShape.curves = tmpPath.curves; | |
| shapes.push( tmpShape ); | |
| } | |
| return shapes; | |
| } | |
| function isPointInsidePolygon( inPt, inPolygon ) { | |
| const polyLen = inPolygon.length; | |
| // inPt on polygon contour => immediate success or | |
| // toggling of inside/outside at every single! intersection point of an edge | |
| // with the horizontal line through inPt, left of inPt | |
| // not counting lowerY endpoints of edges and whole edges on that line | |
| let inside = false; | |
| for ( let p = polyLen - 1, q = 0; q < polyLen; p = q ++ ) { | |
| let edgeLowPt = inPolygon[ p ]; | |
| let edgeHighPt = inPolygon[ q ]; | |
| let edgeDx = edgeHighPt.x - edgeLowPt.x; | |
| let edgeDy = edgeHighPt.y - edgeLowPt.y; | |
| if ( Math.abs( edgeDy ) > Number.EPSILON ) { | |
| // not parallel | |
| if ( edgeDy < 0 ) { | |
| edgeLowPt = inPolygon[ q ]; edgeDx = - edgeDx; | |
| edgeHighPt = inPolygon[ p ]; edgeDy = - edgeDy; | |
| } | |
| if ( ( inPt.y < edgeLowPt.y ) || ( inPt.y > edgeHighPt.y ) ) continue; | |
| if ( inPt.y === edgeLowPt.y ) { | |
| if ( inPt.x === edgeLowPt.x ) return true; // inPt is on contour ? | |
| // continue; // no intersection or edgeLowPt => doesn't count !!! | |
| } else { | |
| const perpEdge = edgeDy * ( inPt.x - edgeLowPt.x ) - edgeDx * ( inPt.y - edgeLowPt.y ); | |
| if ( perpEdge === 0 ) return true; // inPt is on contour ? | |
| if ( perpEdge < 0 ) continue; | |
| inside = ! inside; // true intersection left of inPt | |
| } | |
| } else { | |
| // parallel or collinear | |
| if ( inPt.y !== edgeLowPt.y ) continue; // parallel | |
| // edge lies on the same horizontal line as inPt | |
| if ( ( ( edgeHighPt.x <= inPt.x ) && ( inPt.x <= edgeLowPt.x ) ) || | |
| ( ( edgeLowPt.x <= inPt.x ) && ( inPt.x <= edgeHighPt.x ) ) ) return true; // inPt: Point on contour ! | |
| // continue; | |
| } | |
| } | |
| return inside; | |
| } | |
| const isClockWise = ShapeUtils.isClockWise; | |
| const subPaths = this.subPaths; | |
| if ( subPaths.length === 0 ) return []; | |
| let solid, tmpPath, tmpShape; | |
| const shapes = []; | |
| if ( subPaths.length === 1 ) { | |
| tmpPath = subPaths[ 0 ]; | |
| tmpShape = new Shape(); | |
| tmpShape.curves = tmpPath.curves; | |
| shapes.push( tmpShape ); | |
| return shapes; | |
| } | |
| let holesFirst = ! isClockWise( subPaths[ 0 ].getPoints() ); | |
| holesFirst = isCCW ? ! holesFirst : holesFirst; | |
| // console.log("Holes first", holesFirst); | |
| const betterShapeHoles = []; | |
| const newShapes = []; | |
| let newShapeHoles = []; | |
| let mainIdx = 0; | |
| let tmpPoints; | |
| newShapes[ mainIdx ] = undefined; | |
| newShapeHoles[ mainIdx ] = []; | |
| for ( let i = 0, l = subPaths.length; i < l; i ++ ) { | |
| tmpPath = subPaths[ i ]; | |
| tmpPoints = tmpPath.getPoints(); | |
| solid = isClockWise( tmpPoints ); | |
| solid = isCCW ? ! solid : solid; | |
| if ( solid ) { | |
| if ( ( ! holesFirst ) && ( newShapes[ mainIdx ] ) ) mainIdx ++; | |
| newShapes[ mainIdx ] = { s: new Shape(), p: tmpPoints }; | |
| newShapes[ mainIdx ].s.curves = tmpPath.curves; | |
| if ( holesFirst ) mainIdx ++; | |
| newShapeHoles[ mainIdx ] = []; | |
| //console.log('cw', i); | |
| } else { | |
| newShapeHoles[ mainIdx ].push( { h: tmpPath, p: tmpPoints[ 0 ] } ); | |
| //console.log('ccw', i); | |
| } | |
| } | |
| // only Holes? -> probably all Shapes with wrong orientation | |
| if ( ! newShapes[ 0 ] ) return toShapesNoHoles( subPaths ); | |
| if ( newShapes.length > 1 ) { | |
| let ambiguous = false; | |
| let toChange = 0; | |
| for ( let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) { | |
| betterShapeHoles[ sIdx ] = []; | |
| } | |
| for ( let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) { | |
| const sho = newShapeHoles[ sIdx ]; | |
| for ( let hIdx = 0; hIdx < sho.length; hIdx ++ ) { | |
| const ho = sho[ hIdx ]; | |
| let hole_unassigned = true; | |
| for ( let s2Idx = 0; s2Idx < newShapes.length; s2Idx ++ ) { | |
| if ( isPointInsidePolygon( ho.p, newShapes[ s2Idx ].p ) ) { | |
| if ( sIdx !== s2Idx ) toChange ++; | |
| if ( hole_unassigned ) { | |
| hole_unassigned = false; | |
| betterShapeHoles[ s2Idx ].push( ho ); | |
| } else { | |
| ambiguous = true; | |
| } | |
| } | |
| } | |
| if ( hole_unassigned ) { | |
| betterShapeHoles[ sIdx ].push( ho ); | |
| } | |
| } | |
| } | |
| if ( toChange > 0 && ambiguous === false ) { | |
| newShapeHoles = betterShapeHoles; | |
| } | |
| } | |
| let tmpHoles; | |
| for ( let i = 0, il = newShapes.length; i < il; i ++ ) { | |
| tmpShape = newShapes[ i ].s; | |
| shapes.push( tmpShape ); | |
| tmpHoles = newShapeHoles[ i ]; | |
| for ( let j = 0, jl = tmpHoles.length; j < jl; j ++ ) { | |
| tmpShape.holes.push( tmpHoles[ j ].h ); | |
| } | |
| } | |
| //console.log("shape", shapes); | |
| return shapes; | |
| } | |
| } | |
| export { ShapePath }; | |
Xet Storage Details
- Size:
- 8.39 kB
- Xet hash:
- d07433a873e41813a6563e99636213bc5a73a1ce5e9e0147974c2da00157263e
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.