webgl-sdf-generator
Advanced tools
Comparing version 0.1.0 to 1.0.1
@@ -24,14 +24,19 @@ export default function SDFGenerator() { | ||
/** | ||
* Convert a path string to a series of straight line segments | ||
* Parse a path string into its constituent line/curve commands, invoking a callback for each. | ||
* @param {string} pathString - An SVG-like path string to parse; should only contain commands: M/L/Q/C/Z | ||
* @param {function(x1:number, y1:number, x2:number, y2:number)} segmentCallback - A callback | ||
* function that will be called once for every line segment | ||
* @param {number} [curvePoints] - How many straight line segments to use when approximating a | ||
* bezier curve in the path. Defaults to 16. | ||
* @param {function( | ||
* command: 'L'|'Q'|'C', | ||
* startX: number, | ||
* startY: number, | ||
* endX: number, | ||
* endY: number, | ||
* ctrl1X?: number, | ||
* ctrl1Y?: number, | ||
* ctrl2X?: number, | ||
* ctrl2Y?: number | ||
* )} commandCallback - A callback function that will be called once for each parsed path command, passing the | ||
* command identifier (only L/Q/C commands) and its numeric arguments. | ||
*/ | ||
function pathToLineSegments (pathString, segmentCallback, curvePoints) { | ||
if ( curvePoints === void 0 ) curvePoints = 16; | ||
function forEachPathCommand(pathString, commandCallback) { | ||
var segmentRE = /([MLQCZ])([^MLQCZ]*)/g; | ||
var tempPoint = { x: 0, y: 0 }; | ||
var match, firstX, firstY, prevX, prevY; | ||
@@ -49,18 +54,48 @@ while ((match = segmentRE.exec(pathString))) { | ||
case 'L': | ||
if (args[0] !== prevX || args[1] !== prevY) { | ||
//yup, some fonts have zero-length line commands | ||
segmentCallback(prevX, prevY, (prevX = args[0]), (prevY = args[1])); | ||
if (args[0] !== prevX || args[1] !== prevY) { // yup, some fonts have zero-length line commands | ||
commandCallback('L', prevX, prevY, (prevX = args[0]), (prevY = args[1])); | ||
} | ||
break | ||
case 'Q': { | ||
var prevCurveX = prevX; | ||
var prevCurveY = prevY; | ||
commandCallback('Q', prevX, prevY, (prevX = args[2]), (prevY = args[3]), args[0], args[1]); | ||
break | ||
} | ||
case 'C': { | ||
commandCallback('C', prevX, prevY, (prevX = args[4]), (prevY = args[5]), args[0], args[1], args[2], args[3]); | ||
break | ||
} | ||
case 'Z': | ||
if (prevX !== firstX || prevY !== firstY) { | ||
commandCallback('L', prevX, prevY, firstX, firstY); | ||
} | ||
break | ||
} | ||
} | ||
} | ||
/** | ||
* Convert a path string to a series of straight line segments | ||
* @param {string} pathString - An SVG-like path string to parse; should only contain commands: M/L/Q/C/Z | ||
* @param {function(x1:number, y1:number, x2:number, y2:number)} segmentCallback - A callback | ||
* function that will be called once for every line segment | ||
* @param {number} [curvePoints] - How many straight line segments to use when approximating a | ||
* bezier curve in the path. Defaults to 16. | ||
*/ | ||
function pathToLineSegments (pathString, segmentCallback, curvePoints) { | ||
if ( curvePoints === void 0 ) curvePoints = 16; | ||
var tempPoint = { x: 0, y: 0 }; | ||
forEachPathCommand(pathString, function (command, startX, startY, endX, endY, ctrl1X, ctrl1Y, ctrl2X, ctrl2Y) { | ||
switch (command) { | ||
case 'L': | ||
segmentCallback(startX, startY, endX, endY); | ||
break | ||
case 'Q': { | ||
var prevCurveX = startX; | ||
var prevCurveY = startY; | ||
for (var i = 1; i < curvePoints; i++) { | ||
pointOnQuadraticBezier( | ||
prevX, | ||
prevY, | ||
args[0], | ||
args[1], | ||
args[2], | ||
args[3], | ||
startX, startY, | ||
ctrl1X, ctrl1Y, | ||
endX, endY, | ||
i / (curvePoints - 1), | ||
@@ -73,19 +108,13 @@ tempPoint | ||
} | ||
prevX = args[2]; | ||
prevY = args[3]; | ||
break | ||
} | ||
case 'C': { | ||
var prevCurveX$1 = prevX; | ||
var prevCurveY$1 = prevY; | ||
var prevCurveX$1 = startX; | ||
var prevCurveY$1 = startY; | ||
for (var i$1 = 1; i$1 < curvePoints; i$1++) { | ||
pointOnCubicBezier( | ||
prevX, | ||
prevY, | ||
args[0], | ||
args[1], | ||
args[2], | ||
args[3], | ||
args[4], | ||
args[5], | ||
startX, startY, | ||
ctrl1X, ctrl1Y, | ||
ctrl2X, ctrl2Y, | ||
endX, endY, | ||
i$1 / (curvePoints - 1), | ||
@@ -98,16 +127,9 @@ tempPoint | ||
} | ||
prevX = args[4]; | ||
prevY = args[5]; | ||
break | ||
} | ||
case 'Z': | ||
if (prevX !== firstX || prevY !== firstY) { | ||
segmentCallback(prevX, prevY, firstX, firstY); | ||
} | ||
break | ||
} | ||
} | ||
}); | ||
} | ||
function generateSDFWithJS(sdfWidth, sdfHeight, path, viewBox, maxDistance, sdfExponent) { | ||
function generate$2 (sdfWidth, sdfHeight, path, viewBox, maxDistance, sdfExponent) { | ||
if ( sdfExponent === void 0 ) sdfExponent = 1; | ||
@@ -166,3 +188,3 @@ | ||
*/ | ||
function findNearestSignedDistance(x, y) { | ||
function findNearestSignedDistance (x, y) { | ||
var closestDistSq = Infinity; | ||
@@ -194,3 +216,3 @@ var closestDist = Infinity; | ||
*/ | ||
function isPointInPoly(x, y) { | ||
function isPointInPoly (x, y) { | ||
var inside = false; | ||
@@ -212,3 +234,3 @@ for (var i = segments.length; i--;) { | ||
*/ | ||
function absSquareDistanceToLineSegment(x, y, lineX0, lineY0, lineX1, lineY1) { | ||
function absSquareDistanceToLineSegment (x, y, lineX0, lineY0, lineX1, lineY1) { | ||
var ldx = lineX1 - lineX0; | ||
@@ -223,6 +245,11 @@ var ldy = lineY1 - lineY0; | ||
var vertexShader = "precision highp float;\nuniform vec4 uGlyphBounds;\nattribute vec2 aUV;\nattribute vec4 aLineSegment;\nvarying vec4 vLineSegment;\nvarying vec2 vGlyphXY;\nvarying vec2 vUV;\n\nvoid main() {\n vLineSegment = aLineSegment;\n vGlyphXY = mix(uGlyphBounds.xy, uGlyphBounds.zw, aUV);\n vUV = aUV;\n gl_Position = vec4(mix(vec2(-1.0), vec2(1.0), aUV), 0.0, 1.0);\n}\n"; | ||
var javascript = /*#__PURE__*/Object.freeze({ | ||
__proto__: null, | ||
generate: generate$2 | ||
}); | ||
var fragmentShader = "precision highp float;\nuniform vec4 uGlyphBounds;\nuniform float uMaxDistance;\nuniform float uExponent;\nvarying vec4 vLineSegment;\nvarying vec2 vGlyphXY;\nvarying vec2 vUV;\n\nfloat absDistToSegment(vec2 point, vec2 lineA, vec2 lineB) {\n vec2 lineDir = lineB - lineA;\n float lenSq = dot(lineDir, lineDir);\n float t = lenSq == 0.0 ? 0.0 : clamp(dot(point - lineA, lineDir) / lenSq, 0.0, 1.0);\n vec2 linePt = lineA + t * lineDir;\n return distance(point, linePt);\n}\n\nbool isCrossing(vec2 point, vec2 lineA, vec2 lineB) {\n return (lineA.y > point.y != lineB.y > point.y) &&\n (point.x < (lineB.x - lineA.x) * (point.y - lineA.y) / (lineB.y - lineA.y) + lineA.x);\n}\n\nvoid main() {\n float dist = absDistToSegment(vGlyphXY, vLineSegment.xy, vLineSegment.zw);\n float val = pow(1.0 - clamp(dist / uMaxDistance, 0.0, 1.0), uExponent) * 0.5;\n bool crossing = isCrossing(vGlyphXY, vLineSegment.xy, vLineSegment.zw);\n gl_FragColor = vec4(val, 0.0, 0.0, crossing ? 1.0 / 256.0 : 0.0);\n}\n"; | ||
var vertexShader = "precision highp float;\nuniform vec4 uGlyphBounds;\nattribute vec2 aUV;\nattribute vec4 aLineSegment;\nvarying vec4 vLineSegment;\nvarying vec2 vGlyphXY;\n\nvoid main() {\n vLineSegment = aLineSegment;\n vGlyphXY = mix(uGlyphBounds.xy, uGlyphBounds.zw, aUV);\n gl_Position = vec4(mix(vec2(-1.0), vec2(1.0), aUV), 0.0, 1.0);\n}\n"; | ||
var fragmentShader = "precision highp float;\nuniform vec4 uGlyphBounds;\nuniform float uMaxDistance;\nuniform float uExponent;\nvarying vec4 vLineSegment;\nvarying vec2 vGlyphXY;\n\nfloat absDistToSegment(vec2 point, vec2 lineA, vec2 lineB) {\n vec2 lineDir = lineB - lineA;\n float lenSq = dot(lineDir, lineDir);\n float t = lenSq == 0.0 ? 0.0 : clamp(dot(point - lineA, lineDir) / lenSq, 0.0, 1.0);\n vec2 linePt = lineA + t * lineDir;\n return distance(point, linePt);\n}\n\nbool isCrossing(vec2 point, vec2 lineA, vec2 lineB) {\n return (lineA.y > point.y != lineB.y > point.y) &&\n (point.x < (lineB.x - lineA.x) * (point.y - lineA.y) / (lineB.y - lineA.y) + lineA.x);\n}\n\nvoid main() {\n float dist = absDistToSegment(vGlyphXY, vLineSegment.xy, vLineSegment.zw);\n float val = pow(1.0 - clamp(dist / uMaxDistance, 0.0, 1.0), uExponent) * 0.5;\n bool crossing = isCrossing(vGlyphXY, vLineSegment.xy, vLineSegment.zw);\n gl_FragColor = vec4(val, 0.0, 0.0, crossing ? 1.0 / 256.0 : 0.0);\n}\n"; | ||
var viewportUVs = new Float32Array([0, 0, 2, 0, 0, 2]); | ||
@@ -235,7 +262,4 @@ | ||
var program; | ||
var viewportPosBuffer; | ||
var lineSegmentsBuffer; | ||
var boundsUniform; | ||
var maxDistUniform; | ||
var expUniform; | ||
var buffers = {}; | ||
var uniforms = {}; | ||
var lastWidth; | ||
@@ -247,4 +271,5 @@ var lastHeight; | ||
function handleContextLoss () { | ||
instancingExtension = blendMinMaxExtension = program = viewportPosBuffer = | ||
lineSegmentsBuffer = boundsUniform = maxDistUniform = expUniform = lastWidth = lastHeight = undefined; | ||
instancingExtension = blendMinMaxExtension = program = lastWidth = lastHeight = undefined; | ||
buffers = {}; | ||
uniforms = {}; | ||
} | ||
@@ -263,7 +288,31 @@ | ||
function generateSDFWithWebGL (sdfWidth, sdfHeight, path, viewBox, maxDistance, sdfExponent) { | ||
function setAttributeBuffer(name, size, usage, instancingDivisor, data) { | ||
if (!buffers[name]) { | ||
gl.bindBuffer(gl.ARRAY_BUFFER, buffers[name] = gl.createBuffer()); | ||
var attrLocation = gl.getAttribLocation(program, name); | ||
gl.vertexAttribPointer(attrLocation, size, gl.FLOAT, false, 0, 0); | ||
gl.enableVertexAttribArray(attrLocation); | ||
if (instancingDivisor) { | ||
instancingExtension.vertexAttribDivisorANGLE(attrLocation, instancingDivisor); | ||
} | ||
} | ||
gl.bindBuffer(gl.ARRAY_BUFFER, buffers[name]); | ||
gl.bufferData(gl.ARRAY_BUFFER, data, usage); | ||
} | ||
function setUniform(name) { | ||
var values = [], len = arguments.length - 1; | ||
while ( len-- > 0 ) values[ len ] = arguments[ len + 1 ]; | ||
if (!uniforms[name]) { | ||
uniforms[name] = gl.getUniformLocation(program, name); | ||
} | ||
gl[("uniform" + (values.length) + "f")].apply(gl, [ uniforms[name] ].concat( values )); | ||
} | ||
function generate$1 (sdfWidth, sdfHeight, path, viewBox, maxDistance, sdfExponent) { | ||
if ( sdfExponent === void 0 ) sdfExponent = 1; | ||
// Verify support | ||
if (!isTestingSupport && !supportsWebGLGeneration()) { | ||
if (!isTestingSupport && !isSupported()) { | ||
throw new Error('WebGL generation not supported') | ||
@@ -329,33 +378,11 @@ } | ||
// Init attributes | ||
if (!viewportPosBuffer) { | ||
viewportPosBuffer = gl.createBuffer(); | ||
gl.bindBuffer(gl.ARRAY_BUFFER, viewportPosBuffer); | ||
gl.bufferData(gl.ARRAY_BUFFER, viewportUVs, gl.STATIC_DRAW); | ||
var viewportPosAttrLoc = gl.getAttribLocation(program, 'aUV'); | ||
gl.vertexAttribPointer(viewportPosAttrLoc, 2, gl.FLOAT, false, 0, 0); | ||
gl.enableVertexAttribArray(viewportPosAttrLoc); | ||
} | ||
if (!lineSegmentsBuffer) { | ||
lineSegmentsBuffer = gl.createBuffer(); | ||
gl.bindBuffer(gl.ARRAY_BUFFER, lineSegmentsBuffer); | ||
gl.bufferData(gl.ARRAY_BUFFER, lineSegmentCoords, gl.DYNAMIC_DRAW); | ||
var lineSegmentsAttrLoc = gl.getAttribLocation(program, 'aLineSegment'); | ||
gl.vertexAttribPointer(lineSegmentsAttrLoc, 4, gl.FLOAT, false, 0, 0); | ||
gl.enableVertexAttribArray(lineSegmentsAttrLoc); | ||
instancingExtension.vertexAttribDivisorANGLE(lineSegmentsAttrLoc, 1); | ||
} | ||
gl.bindBuffer(gl.ARRAY_BUFFER, lineSegmentsBuffer); | ||
gl.bufferData(gl.ARRAY_BUFFER, lineSegmentCoords, gl.DYNAMIC_DRAW); | ||
// Init/update attributes | ||
setAttributeBuffer('aUV', 2, gl.STATIC_DRAW, 0, viewportUVs); | ||
setAttributeBuffer('aLineSegment', 4, gl.DYNAMIC_DRAW, 1, lineSegmentCoords); | ||
// Init uniforms | ||
boundsUniform = boundsUniform || gl.getUniformLocation(program, 'uGlyphBounds'); | ||
gl.uniform4f.apply(gl, [ boundsUniform ].concat( viewBox )); | ||
// Init/update uniforms | ||
setUniform.apply(void 0, [ 'uGlyphBounds' ].concat( viewBox )); | ||
setUniform('uMaxDistance', maxDistance); | ||
setUniform('uExponent', sdfExponent); | ||
maxDistUniform = maxDistUniform || gl.getUniformLocation(program, 'uMaxDistance'); | ||
gl.uniform1f(maxDistUniform, maxDistance); | ||
expUniform = expUniform || gl.getUniformLocation(program, 'uExponent'); | ||
gl.uniform1f(expUniform, sdfExponent); | ||
// Draw | ||
@@ -393,3 +420,3 @@ gl.clear(gl.COLOR_BUFFER_BIT); | ||
function supportsWebGLGeneration () { | ||
function isSupported () { | ||
if (supported === null) { | ||
@@ -407,3 +434,3 @@ isTestingSupport = true; | ||
]; | ||
var testResult = generateSDFWithWebGL( | ||
var testResult = generate$1( | ||
4, | ||
@@ -434,2 +461,8 @@ 4, | ||
var webgl = /*#__PURE__*/Object.freeze({ | ||
__proto__: null, | ||
generate: generate$1, | ||
isSupported: isSupported | ||
}); | ||
/** | ||
@@ -447,3 +480,3 @@ * Generate an SDF texture image for a 2D path. | ||
*/ | ||
function generateSDF( | ||
function generate( | ||
sdfWidth, | ||
@@ -460,14 +493,14 @@ sdfHeight, | ||
try { | ||
return generateSDFWithWebGL(sdfWidth, sdfHeight, path, viewBox, maxDistance, sdfExponent) | ||
return generate$1(sdfWidth, sdfHeight, path, viewBox, maxDistance, sdfExponent) | ||
} catch(e) { | ||
console.info('WebGL SDF generation failed, falling back to JS', e); | ||
return generateSDFWithJS(sdfWidth, sdfHeight, path, viewBox, maxDistance, sdfExponent) | ||
return generate$2(sdfWidth, sdfHeight, path, viewBox, maxDistance, sdfExponent) | ||
} | ||
} | ||
exports.generateSDF = generateSDF; | ||
exports.generateSDFWithJS = generateSDFWithJS; | ||
exports.generateSDFWithWebGL = generateSDFWithWebGL; | ||
exports.forEachPathCommand = forEachPathCommand; | ||
exports.generate = generate; | ||
exports.javascript = javascript; | ||
exports.pathToLineSegments = pathToLineSegments; | ||
exports.supportsWebGLGeneration = supportsWebGLGeneration; | ||
exports.webgl = webgl; | ||
@@ -474,0 +507,0 @@ Object.defineProperty(exports, '__esModule', { value: true }); |
@@ -29,14 +29,19 @@ (function (global, factory) { | ||
/** | ||
* Convert a path string to a series of straight line segments | ||
* Parse a path string into its constituent line/curve commands, invoking a callback for each. | ||
* @param {string} pathString - An SVG-like path string to parse; should only contain commands: M/L/Q/C/Z | ||
* @param {function(x1:number, y1:number, x2:number, y2:number)} segmentCallback - A callback | ||
* function that will be called once for every line segment | ||
* @param {number} [curvePoints] - How many straight line segments to use when approximating a | ||
* bezier curve in the path. Defaults to 16. | ||
* @param {function( | ||
* command: 'L'|'Q'|'C', | ||
* startX: number, | ||
* startY: number, | ||
* endX: number, | ||
* endY: number, | ||
* ctrl1X?: number, | ||
* ctrl1Y?: number, | ||
* ctrl2X?: number, | ||
* ctrl2Y?: number | ||
* )} commandCallback - A callback function that will be called once for each parsed path command, passing the | ||
* command identifier (only L/Q/C commands) and its numeric arguments. | ||
*/ | ||
function pathToLineSegments (pathString, segmentCallback, curvePoints) { | ||
if ( curvePoints === void 0 ) curvePoints = 16; | ||
function forEachPathCommand(pathString, commandCallback) { | ||
var segmentRE = /([MLQCZ])([^MLQCZ]*)/g; | ||
var tempPoint = { x: 0, y: 0 }; | ||
var match, firstX, firstY, prevX, prevY; | ||
@@ -54,18 +59,48 @@ while ((match = segmentRE.exec(pathString))) { | ||
case 'L': | ||
if (args[0] !== prevX || args[1] !== prevY) { | ||
//yup, some fonts have zero-length line commands | ||
segmentCallback(prevX, prevY, (prevX = args[0]), (prevY = args[1])); | ||
if (args[0] !== prevX || args[1] !== prevY) { // yup, some fonts have zero-length line commands | ||
commandCallback('L', prevX, prevY, (prevX = args[0]), (prevY = args[1])); | ||
} | ||
break | ||
case 'Q': { | ||
var prevCurveX = prevX; | ||
var prevCurveY = prevY; | ||
commandCallback('Q', prevX, prevY, (prevX = args[2]), (prevY = args[3]), args[0], args[1]); | ||
break | ||
} | ||
case 'C': { | ||
commandCallback('C', prevX, prevY, (prevX = args[4]), (prevY = args[5]), args[0], args[1], args[2], args[3]); | ||
break | ||
} | ||
case 'Z': | ||
if (prevX !== firstX || prevY !== firstY) { | ||
commandCallback('L', prevX, prevY, firstX, firstY); | ||
} | ||
break | ||
} | ||
} | ||
} | ||
/** | ||
* Convert a path string to a series of straight line segments | ||
* @param {string} pathString - An SVG-like path string to parse; should only contain commands: M/L/Q/C/Z | ||
* @param {function(x1:number, y1:number, x2:number, y2:number)} segmentCallback - A callback | ||
* function that will be called once for every line segment | ||
* @param {number} [curvePoints] - How many straight line segments to use when approximating a | ||
* bezier curve in the path. Defaults to 16. | ||
*/ | ||
function pathToLineSegments (pathString, segmentCallback, curvePoints) { | ||
if ( curvePoints === void 0 ) curvePoints = 16; | ||
var tempPoint = { x: 0, y: 0 }; | ||
forEachPathCommand(pathString, function (command, startX, startY, endX, endY, ctrl1X, ctrl1Y, ctrl2X, ctrl2Y) { | ||
switch (command) { | ||
case 'L': | ||
segmentCallback(startX, startY, endX, endY); | ||
break | ||
case 'Q': { | ||
var prevCurveX = startX; | ||
var prevCurveY = startY; | ||
for (var i = 1; i < curvePoints; i++) { | ||
pointOnQuadraticBezier( | ||
prevX, | ||
prevY, | ||
args[0], | ||
args[1], | ||
args[2], | ||
args[3], | ||
startX, startY, | ||
ctrl1X, ctrl1Y, | ||
endX, endY, | ||
i / (curvePoints - 1), | ||
@@ -78,19 +113,13 @@ tempPoint | ||
} | ||
prevX = args[2]; | ||
prevY = args[3]; | ||
break | ||
} | ||
case 'C': { | ||
var prevCurveX$1 = prevX; | ||
var prevCurveY$1 = prevY; | ||
var prevCurveX$1 = startX; | ||
var prevCurveY$1 = startY; | ||
for (var i$1 = 1; i$1 < curvePoints; i$1++) { | ||
pointOnCubicBezier( | ||
prevX, | ||
prevY, | ||
args[0], | ||
args[1], | ||
args[2], | ||
args[3], | ||
args[4], | ||
args[5], | ||
startX, startY, | ||
ctrl1X, ctrl1Y, | ||
ctrl2X, ctrl2Y, | ||
endX, endY, | ||
i$1 / (curvePoints - 1), | ||
@@ -103,16 +132,9 @@ tempPoint | ||
} | ||
prevX = args[4]; | ||
prevY = args[5]; | ||
break | ||
} | ||
case 'Z': | ||
if (prevX !== firstX || prevY !== firstY) { | ||
segmentCallback(prevX, prevY, firstX, firstY); | ||
} | ||
break | ||
} | ||
} | ||
}); | ||
} | ||
function generateSDFWithJS(sdfWidth, sdfHeight, path, viewBox, maxDistance, sdfExponent) { | ||
function generate$2 (sdfWidth, sdfHeight, path, viewBox, maxDistance, sdfExponent) { | ||
if ( sdfExponent === void 0 ) sdfExponent = 1; | ||
@@ -171,3 +193,3 @@ | ||
*/ | ||
function findNearestSignedDistance(x, y) { | ||
function findNearestSignedDistance (x, y) { | ||
var closestDistSq = Infinity; | ||
@@ -199,3 +221,3 @@ var closestDist = Infinity; | ||
*/ | ||
function isPointInPoly(x, y) { | ||
function isPointInPoly (x, y) { | ||
var inside = false; | ||
@@ -217,3 +239,3 @@ for (var i = segments.length; i--;) { | ||
*/ | ||
function absSquareDistanceToLineSegment(x, y, lineX0, lineY0, lineX1, lineY1) { | ||
function absSquareDistanceToLineSegment (x, y, lineX0, lineY0, lineX1, lineY1) { | ||
var ldx = lineX1 - lineX0; | ||
@@ -228,6 +250,11 @@ var ldy = lineY1 - lineY0; | ||
var vertexShader = "precision highp float;\nuniform vec4 uGlyphBounds;\nattribute vec2 aUV;\nattribute vec4 aLineSegment;\nvarying vec4 vLineSegment;\nvarying vec2 vGlyphXY;\nvarying vec2 vUV;\n\nvoid main() {\n vLineSegment = aLineSegment;\n vGlyphXY = mix(uGlyphBounds.xy, uGlyphBounds.zw, aUV);\n vUV = aUV;\n gl_Position = vec4(mix(vec2(-1.0), vec2(1.0), aUV), 0.0, 1.0);\n}\n"; | ||
var javascript = /*#__PURE__*/Object.freeze({ | ||
__proto__: null, | ||
generate: generate$2 | ||
}); | ||
var fragmentShader = "precision highp float;\nuniform vec4 uGlyphBounds;\nuniform float uMaxDistance;\nuniform float uExponent;\nvarying vec4 vLineSegment;\nvarying vec2 vGlyphXY;\nvarying vec2 vUV;\n\nfloat absDistToSegment(vec2 point, vec2 lineA, vec2 lineB) {\n vec2 lineDir = lineB - lineA;\n float lenSq = dot(lineDir, lineDir);\n float t = lenSq == 0.0 ? 0.0 : clamp(dot(point - lineA, lineDir) / lenSq, 0.0, 1.0);\n vec2 linePt = lineA + t * lineDir;\n return distance(point, linePt);\n}\n\nbool isCrossing(vec2 point, vec2 lineA, vec2 lineB) {\n return (lineA.y > point.y != lineB.y > point.y) &&\n (point.x < (lineB.x - lineA.x) * (point.y - lineA.y) / (lineB.y - lineA.y) + lineA.x);\n}\n\nvoid main() {\n float dist = absDistToSegment(vGlyphXY, vLineSegment.xy, vLineSegment.zw);\n float val = pow(1.0 - clamp(dist / uMaxDistance, 0.0, 1.0), uExponent) * 0.5;\n bool crossing = isCrossing(vGlyphXY, vLineSegment.xy, vLineSegment.zw);\n gl_FragColor = vec4(val, 0.0, 0.0, crossing ? 1.0 / 256.0 : 0.0);\n}\n"; | ||
var vertexShader = "precision highp float;\nuniform vec4 uGlyphBounds;\nattribute vec2 aUV;\nattribute vec4 aLineSegment;\nvarying vec4 vLineSegment;\nvarying vec2 vGlyphXY;\n\nvoid main() {\n vLineSegment = aLineSegment;\n vGlyphXY = mix(uGlyphBounds.xy, uGlyphBounds.zw, aUV);\n gl_Position = vec4(mix(vec2(-1.0), vec2(1.0), aUV), 0.0, 1.0);\n}\n"; | ||
var fragmentShader = "precision highp float;\nuniform vec4 uGlyphBounds;\nuniform float uMaxDistance;\nuniform float uExponent;\nvarying vec4 vLineSegment;\nvarying vec2 vGlyphXY;\n\nfloat absDistToSegment(vec2 point, vec2 lineA, vec2 lineB) {\n vec2 lineDir = lineB - lineA;\n float lenSq = dot(lineDir, lineDir);\n float t = lenSq == 0.0 ? 0.0 : clamp(dot(point - lineA, lineDir) / lenSq, 0.0, 1.0);\n vec2 linePt = lineA + t * lineDir;\n return distance(point, linePt);\n}\n\nbool isCrossing(vec2 point, vec2 lineA, vec2 lineB) {\n return (lineA.y > point.y != lineB.y > point.y) &&\n (point.x < (lineB.x - lineA.x) * (point.y - lineA.y) / (lineB.y - lineA.y) + lineA.x);\n}\n\nvoid main() {\n float dist = absDistToSegment(vGlyphXY, vLineSegment.xy, vLineSegment.zw);\n float val = pow(1.0 - clamp(dist / uMaxDistance, 0.0, 1.0), uExponent) * 0.5;\n bool crossing = isCrossing(vGlyphXY, vLineSegment.xy, vLineSegment.zw);\n gl_FragColor = vec4(val, 0.0, 0.0, crossing ? 1.0 / 256.0 : 0.0);\n}\n"; | ||
var viewportUVs = new Float32Array([0, 0, 2, 0, 0, 2]); | ||
@@ -240,7 +267,4 @@ | ||
var program; | ||
var viewportPosBuffer; | ||
var lineSegmentsBuffer; | ||
var boundsUniform; | ||
var maxDistUniform; | ||
var expUniform; | ||
var buffers = {}; | ||
var uniforms = {}; | ||
var lastWidth; | ||
@@ -252,4 +276,5 @@ var lastHeight; | ||
function handleContextLoss () { | ||
instancingExtension = blendMinMaxExtension = program = viewportPosBuffer = | ||
lineSegmentsBuffer = boundsUniform = maxDistUniform = expUniform = lastWidth = lastHeight = undefined; | ||
instancingExtension = blendMinMaxExtension = program = lastWidth = lastHeight = undefined; | ||
buffers = {}; | ||
uniforms = {}; | ||
} | ||
@@ -268,7 +293,31 @@ | ||
function generateSDFWithWebGL (sdfWidth, sdfHeight, path, viewBox, maxDistance, sdfExponent) { | ||
function setAttributeBuffer(name, size, usage, instancingDivisor, data) { | ||
if (!buffers[name]) { | ||
gl.bindBuffer(gl.ARRAY_BUFFER, buffers[name] = gl.createBuffer()); | ||
var attrLocation = gl.getAttribLocation(program, name); | ||
gl.vertexAttribPointer(attrLocation, size, gl.FLOAT, false, 0, 0); | ||
gl.enableVertexAttribArray(attrLocation); | ||
if (instancingDivisor) { | ||
instancingExtension.vertexAttribDivisorANGLE(attrLocation, instancingDivisor); | ||
} | ||
} | ||
gl.bindBuffer(gl.ARRAY_BUFFER, buffers[name]); | ||
gl.bufferData(gl.ARRAY_BUFFER, data, usage); | ||
} | ||
function setUniform(name) { | ||
var values = [], len = arguments.length - 1; | ||
while ( len-- > 0 ) values[ len ] = arguments[ len + 1 ]; | ||
if (!uniforms[name]) { | ||
uniforms[name] = gl.getUniformLocation(program, name); | ||
} | ||
gl[("uniform" + (values.length) + "f")].apply(gl, [ uniforms[name] ].concat( values )); | ||
} | ||
function generate$1 (sdfWidth, sdfHeight, path, viewBox, maxDistance, sdfExponent) { | ||
if ( sdfExponent === void 0 ) sdfExponent = 1; | ||
// Verify support | ||
if (!isTestingSupport && !supportsWebGLGeneration()) { | ||
if (!isTestingSupport && !isSupported()) { | ||
throw new Error('WebGL generation not supported') | ||
@@ -334,33 +383,11 @@ } | ||
// Init attributes | ||
if (!viewportPosBuffer) { | ||
viewportPosBuffer = gl.createBuffer(); | ||
gl.bindBuffer(gl.ARRAY_BUFFER, viewportPosBuffer); | ||
gl.bufferData(gl.ARRAY_BUFFER, viewportUVs, gl.STATIC_DRAW); | ||
var viewportPosAttrLoc = gl.getAttribLocation(program, 'aUV'); | ||
gl.vertexAttribPointer(viewportPosAttrLoc, 2, gl.FLOAT, false, 0, 0); | ||
gl.enableVertexAttribArray(viewportPosAttrLoc); | ||
} | ||
if (!lineSegmentsBuffer) { | ||
lineSegmentsBuffer = gl.createBuffer(); | ||
gl.bindBuffer(gl.ARRAY_BUFFER, lineSegmentsBuffer); | ||
gl.bufferData(gl.ARRAY_BUFFER, lineSegmentCoords, gl.DYNAMIC_DRAW); | ||
var lineSegmentsAttrLoc = gl.getAttribLocation(program, 'aLineSegment'); | ||
gl.vertexAttribPointer(lineSegmentsAttrLoc, 4, gl.FLOAT, false, 0, 0); | ||
gl.enableVertexAttribArray(lineSegmentsAttrLoc); | ||
instancingExtension.vertexAttribDivisorANGLE(lineSegmentsAttrLoc, 1); | ||
} | ||
gl.bindBuffer(gl.ARRAY_BUFFER, lineSegmentsBuffer); | ||
gl.bufferData(gl.ARRAY_BUFFER, lineSegmentCoords, gl.DYNAMIC_DRAW); | ||
// Init/update attributes | ||
setAttributeBuffer('aUV', 2, gl.STATIC_DRAW, 0, viewportUVs); | ||
setAttributeBuffer('aLineSegment', 4, gl.DYNAMIC_DRAW, 1, lineSegmentCoords); | ||
// Init uniforms | ||
boundsUniform = boundsUniform || gl.getUniformLocation(program, 'uGlyphBounds'); | ||
gl.uniform4f.apply(gl, [ boundsUniform ].concat( viewBox )); | ||
// Init/update uniforms | ||
setUniform.apply(void 0, [ 'uGlyphBounds' ].concat( viewBox )); | ||
setUniform('uMaxDistance', maxDistance); | ||
setUniform('uExponent', sdfExponent); | ||
maxDistUniform = maxDistUniform || gl.getUniformLocation(program, 'uMaxDistance'); | ||
gl.uniform1f(maxDistUniform, maxDistance); | ||
expUniform = expUniform || gl.getUniformLocation(program, 'uExponent'); | ||
gl.uniform1f(expUniform, sdfExponent); | ||
// Draw | ||
@@ -398,3 +425,3 @@ gl.clear(gl.COLOR_BUFFER_BIT); | ||
function supportsWebGLGeneration () { | ||
function isSupported () { | ||
if (supported === null) { | ||
@@ -412,3 +439,3 @@ isTestingSupport = true; | ||
]; | ||
var testResult = generateSDFWithWebGL( | ||
var testResult = generate$1( | ||
4, | ||
@@ -439,2 +466,8 @@ 4, | ||
var webgl = /*#__PURE__*/Object.freeze({ | ||
__proto__: null, | ||
generate: generate$1, | ||
isSupported: isSupported | ||
}); | ||
/** | ||
@@ -452,3 +485,3 @@ * Generate an SDF texture image for a 2D path. | ||
*/ | ||
function generateSDF( | ||
function generate( | ||
sdfWidth, | ||
@@ -465,14 +498,14 @@ sdfHeight, | ||
try { | ||
return generateSDFWithWebGL(sdfWidth, sdfHeight, path, viewBox, maxDistance, sdfExponent) | ||
return generate$1(sdfWidth, sdfHeight, path, viewBox, maxDistance, sdfExponent) | ||
} catch(e) { | ||
console.info('WebGL SDF generation failed, falling back to JS', e); | ||
return generateSDFWithJS(sdfWidth, sdfHeight, path, viewBox, maxDistance, sdfExponent) | ||
return generate$2(sdfWidth, sdfHeight, path, viewBox, maxDistance, sdfExponent) | ||
} | ||
} | ||
exports.generateSDF = generateSDF; | ||
exports.generateSDFWithJS = generateSDFWithJS; | ||
exports.generateSDFWithWebGL = generateSDFWithWebGL; | ||
exports.forEachPathCommand = forEachPathCommand; | ||
exports.generate = generate; | ||
exports.javascript = javascript; | ||
exports.pathToLineSegments = pathToLineSegments; | ||
exports.supportsWebGLGeneration = supportsWebGLGeneration; | ||
exports.webgl = webgl; | ||
@@ -479,0 +512,0 @@ Object.defineProperty(exports, '__esModule', { value: true }); |
@@ -1,1 +0,1 @@ | ||
!function(n,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(n="undefined"!=typeof globalThis?globalThis:n||self).webgl_sdf_generator=e()}(this,(function(){"use strict";return function(){return function(n){function e(n,e,t,r,i,a,o,l,f,c){var u=1-f;c.x=u*u*u*n+3*u*u*f*t+3*u*f*f*i+f*f*f*o,c.y=u*u*u*e+3*u*u*f*r+3*u*f*f*a+f*f*f*l}function t(n,t,r){void 0===r&&(r=16);for(var i,a,o,l,f,c,u,s,v,d,m,g,p,y,h=/([MLQCZ])([^MLQCZ]*)/g,x={x:0,y:0};i=h.exec(n);){var A=i[2].replace(/^\s*|\s*$/g,"").split(/[,\s]+/).map((function(n){return parseFloat(n)}));switch(i[1]){case"M":l=a=A[0],f=o=A[1];break;case"L":A[0]===l&&A[1]===f||t(l,f,l=A[0],f=A[1]);break;case"Q":for(var b=l,L=f,E=1;E<r;E++)c=l,u=f,s=A[0],v=A[1],d=A[2],m=A[3],y=void 0,y=1-(g=E/(r-1)),(p=x).x=y*y*c+2*y*g*s+g*g*d,p.y=y*y*u+2*y*g*v+g*g*m,t(b,L,x.x,x.y),b=x.x,L=x.y;l=A[2],f=A[3];break;case"C":for(var S=l,D=f,w=1;w<r;w++)e(l,f,A[0],A[1],A[2],A[3],A[4],A[5],w/(r-1),x),t(S,D,x.x,x.y),S=x.x,D=x.y;l=A[4],f=A[5];break;case"Z":l===a&&f===o||t(l,f,a,o)}}}function r(n,e,r,a,o,l){void 0===l&&(l=1);var f=new Uint8Array(n*e),c=a[2]-a[0],u=a[3]-a[1],s=[];t(r,(function(n,e,t,r){s.push({x1:n,y1:e,x2:t,y2:r,minX:Math.min(n,t),minY:Math.min(e,r),maxX:Math.max(n,t),maxY:Math.max(e,r)})})),s.sort((function(n,e){return n.maxX-e.maxX}));for(var v=0;v<n;v++)for(var d=0;d<e;d++){var m=p(a[0]+c*(v+.5)/n,a[1]+u*(d+.5)/e),g=Math.pow(1-Math.abs(m)/o,l)/2;m<0&&(g=1-g),g=Math.max(0,Math.min(255,Math.round(255*g))),f[d*n+v]=g}return f;function p(n,e){for(var t=1/0,r=1/0,a=s.length;a--;){var o=s[a];if(o.maxX+r<=n)break;if(n+r>o.minX&&e-r<o.maxY&&e+r>o.minY){var l=i(n,e,o.x1,o.y1,o.x2,o.y2);l<t&&(t=l,r=Math.sqrt(t))}}return function(n,e){for(var t=!1,r=s.length;r--;){var i=s[r];if(i.maxX<=n)break;i.y1>e!=i.y2>e&&n<(i.x2-i.x1)*(e-i.y1)/(i.y2-i.y1)+i.x1&&(t=!t)}return t}(n,e)&&(r=-r),r}}function i(n,e,t,r,i,a){var o=i-t,l=a-r,f=o*o+l*l,c=f?Math.max(0,Math.min(1,((n-t)*o+(e-r)*l)/f)):0,u=n-(t+c*o),s=e-(r+c*l);return u*u+s*s}var a,o,l,f,c,u,s,v,d,m,g,p,y=new Float32Array([0,0,2,0,0,2]),h=null,x=!1;function A(){l=f=c=u=s=v=d=m=g=p=void 0}function b(n,e,t){var r=n.createShader(t);if(n.shaderSource(r,e),n.compileShader(r),!n.getShaderParameter(r,n.COMPILE_STATUS)&&!n.isContextLost())throw new Error(n.getShaderInfoLog(r).trim());return r}function L(n,e,r,i,h,L){if(void 0===L&&(L=1),!x&&!E())throw new Error("WebGL generation not supported");var S=[];if(t(r,(function(n,e,t,r){S.push(n,e,t,r)})),S=new Float32Array(S),!a){if(!(a="function"==typeof OffscreenCanvas?new OffscreenCanvas(1,1):"undefined"!=typeof document?document.createElement("canvas"):null))throw new Error("OffscreenCanvas or DOM canvas not supported");a.addEventListener("webglcontextlost",(function(n){A(),n.preventDefault()}),!1)}if(o||(o=a.getContext("webgl",{antialias:!1,depth:!1})),!(l=l||o.getExtension("ANGLE_instanced_arrays")))throw new Error("ANGLE_instanced_arrays not supported");if(!(f=f||o.getExtension("EXT_blend_minmax")))throw new Error("EXT_blend_minmax not supported");if(n===g&&e===p||(g=a.width=n,p=a.height=e,o.viewport(0,0,n,e)),c||(c=o.createProgram(),o.attachShader(c,b(o,"precision highp float;\nuniform vec4 uGlyphBounds;\nattribute vec2 aUV;\nattribute vec4 aLineSegment;\nvarying vec4 vLineSegment;\nvarying vec2 vGlyphXY;\nvarying vec2 vUV;\n\nvoid main() {\n vLineSegment = aLineSegment;\n vGlyphXY = mix(uGlyphBounds.xy, uGlyphBounds.zw, aUV);\n vUV = aUV;\n gl_Position = vec4(mix(vec2(-1.0), vec2(1.0), aUV), 0.0, 1.0);\n}\n",o.VERTEX_SHADER)),o.attachShader(c,b(o,"precision highp float;\nuniform vec4 uGlyphBounds;\nuniform float uMaxDistance;\nuniform float uExponent;\nvarying vec4 vLineSegment;\nvarying vec2 vGlyphXY;\nvarying vec2 vUV;\n\nfloat absDistToSegment(vec2 point, vec2 lineA, vec2 lineB) {\n vec2 lineDir = lineB - lineA;\n float lenSq = dot(lineDir, lineDir);\n float t = lenSq == 0.0 ? 0.0 : clamp(dot(point - lineA, lineDir) / lenSq, 0.0, 1.0);\n vec2 linePt = lineA + t * lineDir;\n return distance(point, linePt);\n}\n\nbool isCrossing(vec2 point, vec2 lineA, vec2 lineB) {\n return (lineA.y > point.y != lineB.y > point.y) &&\n (point.x < (lineB.x - lineA.x) * (point.y - lineA.y) / (lineB.y - lineA.y) + lineA.x);\n}\n\nvoid main() {\n float dist = absDistToSegment(vGlyphXY, vLineSegment.xy, vLineSegment.zw);\n float val = pow(1.0 - clamp(dist / uMaxDistance, 0.0, 1.0), uExponent) * 0.5;\n bool crossing = isCrossing(vGlyphXY, vLineSegment.xy, vLineSegment.zw);\n gl_FragColor = vec4(val, 0.0, 0.0, crossing ? 1.0 / 256.0 : 0.0);\n}\n",o.FRAGMENT_SHADER)),o.linkProgram(c)),o.useProgram(c),!u){u=o.createBuffer(),o.bindBuffer(o.ARRAY_BUFFER,u),o.bufferData(o.ARRAY_BUFFER,y,o.STATIC_DRAW);var D=o.getAttribLocation(c,"aUV");o.vertexAttribPointer(D,2,o.FLOAT,!1,0,0),o.enableVertexAttribArray(D)}if(!s){s=o.createBuffer(),o.bindBuffer(o.ARRAY_BUFFER,s),o.bufferData(o.ARRAY_BUFFER,S,o.DYNAMIC_DRAW);var w=o.getAttribLocation(c,"aLineSegment");o.vertexAttribPointer(w,4,o.FLOAT,!1,0,0),o.enableVertexAttribArray(w),l.vertexAttribDivisorANGLE(w,1)}o.bindBuffer(o.ARRAY_BUFFER,s),o.bufferData(o.ARRAY_BUFFER,S,o.DYNAMIC_DRAW),v=v||o.getUniformLocation(c,"uGlyphBounds"),o.uniform4f.apply(o,[v].concat(i)),d=d||o.getUniformLocation(c,"uMaxDistance"),o.uniform1f(d,h),m=m||o.getUniformLocation(c,"uExponent"),o.uniform1f(m,L),o.clear(o.COLOR_BUFFER_BIT),o.enable(o.BLEND),o.blendFunc(o.ONE,o.ONE),o.blendEquationSeparate(f.MAX_EXT,o.FUNC_ADD),l.drawArraysInstancedANGLE(o.TRIANGLES,0,3,S.length/4);var _=new Uint8Array(n*e*4);if(o.readPixels(0,0,n,e,o.RGBA,o.UNSIGNED_BYTE,_),o.isContextLost())throw A(),new Error("webgl context lost");for(var R=new Uint8Array(n*e),F=0,B=0;F<_.length;F+=4)R[B++]=_[F+3]%2?255-_[F]:_[F];return R}function E(){if(null===h){x=!0;var n=null;try{var e=[97,106,97,61,99,137,118,80,80,118,137,99,61,97,106,97],t=L(4,4,"M8,8L16,8L24,24L16,24Z",[0,0,32,32],24,1);(h=t&&e.length===t.length&&t.every((function(n,t){return n===e[t]})))||(n="bad trial run results")}catch(e){h=!1,n=e}n&&console.info("WebGL SDF generation not supported:",n),x=!1}return h}return n.generateSDF=function(n,e,t,i,a,o){void 0===a&&(a=Math.max(i[2]-i[0],i[3]-i[1])/2),void 0===o&&(o=1);try{return L(n,e,t,i,a,o)}catch(l){return console.info("WebGL SDF generation failed, falling back to JS",l),r(n,e,t,i,a,o)}},n.generateSDFWithJS=r,n.generateSDFWithWebGL=L,n.pathToLineSegments=t,n.supportsWebGLGeneration=E,Object.defineProperty(n,"__esModule",{value:!0}),n}({})}})); | ||
!function(n,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(n="undefined"!=typeof globalThis?globalThis:n||self).webgl_sdf_generator=e()}(this,(function(){"use strict";return function(){var n=function(n){function e(n,e,t,r,a,i,o,l,c,u){var f=1-c;u.x=f*f*f*n+3*f*f*c*t+3*f*c*c*a+c*c*c*o,u.y=f*f*f*e+3*f*f*c*r+3*f*c*c*i+c*c*c*l}function t(n,e){for(var t,r,a,i,o,l=/([MLQCZ])([^MLQCZ]*)/g;t=l.exec(n);){var c=t[2].replace(/^\s*|\s*$/g,"").split(/[,\s]+/).map((function(n){return parseFloat(n)}));switch(t[1]){case"M":i=r=c[0],o=a=c[1];break;case"L":c[0]===i&&c[1]===o||e("L",i,o,i=c[0],o=c[1]);break;case"Q":e("Q",i,o,i=c[2],o=c[3],c[0],c[1]);break;case"C":e("C",i,o,i=c[4],o=c[5],c[0],c[1],c[2],c[3]);break;case"Z":i===r&&o===a||e("L",i,o,r,a)}}}function r(n,r,a){void 0===a&&(a=16);var i={x:0,y:0};t(n,(function(n,t,o,l,c,u,f,s,v){switch(n){case"L":r(t,o,l,c);break;case"Q":for(var d=t,p=o,h=1;h<a;h++)x=o,b=f,A=c,S=void 0,S=1-(E=h/(a-1)),(L=i).x=S*S*t+2*S*E*u+E*E*l,L.y=S*S*x+2*S*E*b+E*E*A,r(d,p,i.x,i.y),d=i.x,p=i.y;break;case"C":for(var m=t,g=o,y=1;y<a;y++)e(t,o,u,f,s,v,l,c,y/(a-1),i),r(m,g,i.x,i.y),m=i.x,g=i.y}var x,b,A,E,L,S}))}function a(n,e,t,a,o,l){void 0===l&&(l=1);var c=new Uint8Array(n*e),u=a[2]-a[0],f=a[3]-a[1],s=[];r(t,(function(n,e,t,r){s.push({x1:n,y1:e,x2:t,y2:r,minX:Math.min(n,t),minY:Math.min(e,r),maxX:Math.max(n,t),maxY:Math.max(e,r)})})),s.sort((function(n,e){return n.maxX-e.maxX}));for(var v=0;v<n;v++)for(var d=0;d<e;d++){var p=m(a[0]+u*(v+.5)/n,a[1]+f*(d+.5)/e),h=Math.pow(1-Math.abs(p)/o,l)/2;p<0&&(h=1-h),h=Math.max(0,Math.min(255,Math.round(255*h))),c[d*n+v]=h}return c;function m(n,e){for(var t=1/0,r=1/0,a=s.length;a--;){var o=s[a];if(o.maxX+r<=n)break;if(n+r>o.minX&&e-r<o.maxY&&e+r>o.minY){var l=i(n,e,o.x1,o.y1,o.x2,o.y2);l<t&&(t=l,r=Math.sqrt(t))}}return function(n,e){for(var t=!1,r=s.length;r--;){var a=s[r];if(a.maxX<=n)break;a.y1>e!=a.y2>e&&n<(a.x2-a.x1)*(e-a.y1)/(a.y2-a.y1)+a.x1&&(t=!t)}return t}(n,e)&&(r=-r),r}}function i(n,e,t,r,a,i){var o=a-t,l=i-r,c=o*o+l*l,u=c?Math.max(0,Math.min(1,((n-t)*o+(e-r)*l)/c)):0,f=n-(t+u*o),s=e-(r+u*l);return f*f+s*s}var o,l,c,u,f,s,v,d=Object.freeze({__proto__:null,generate:a}),p=new Float32Array([0,0,2,0,0,2]),h={},m={},g=null,y=!1;function x(){c=u=f=s=v=void 0,h={},m={}}function b(n,e,t){var r=n.createShader(t);if(n.shaderSource(r,e),n.compileShader(r),!n.getShaderParameter(r,n.COMPILE_STATUS)&&!n.isContextLost())throw new Error(n.getShaderInfoLog(r).trim());return r}function A(n,e,t,r,a){if(!h[n]){l.bindBuffer(l.ARRAY_BUFFER,h[n]=l.createBuffer());var i=l.getAttribLocation(f,n);l.vertexAttribPointer(i,e,l.FLOAT,!1,0,0),l.enableVertexAttribArray(i),r&&c.vertexAttribDivisorANGLE(i,r)}l.bindBuffer(l.ARRAY_BUFFER,h[n]),l.bufferData(l.ARRAY_BUFFER,a,t)}function E(n){for(var e=[],t=arguments.length-1;t-- >0;)e[t]=arguments[t+1];m[n]||(m[n]=l.getUniformLocation(f,n)),l["uniform"+e.length+"f"].apply(l,[m[n]].concat(e))}function L(n,e,t,a,i,d){if(void 0===d&&(d=1),!y&&!S())throw new Error("WebGL generation not supported");var h=[];if(r(t,(function(n,e,t,r){h.push(n,e,t,r)})),h=new Float32Array(h),!o){if(!(o="function"==typeof OffscreenCanvas?new OffscreenCanvas(1,1):"undefined"!=typeof document?document.createElement("canvas"):null))throw new Error("OffscreenCanvas or DOM canvas not supported");o.addEventListener("webglcontextlost",(function(n){x(),n.preventDefault()}),!1)}if(l||(l=o.getContext("webgl",{antialias:!1,depth:!1})),!(c=c||l.getExtension("ANGLE_instanced_arrays")))throw new Error("ANGLE_instanced_arrays not supported");if(!(u=u||l.getExtension("EXT_blend_minmax")))throw new Error("EXT_blend_minmax not supported");n===s&&e===v||(s=o.width=n,v=o.height=e,l.viewport(0,0,n,e)),f||(f=l.createProgram(),l.attachShader(f,b(l,"precision highp float;\nuniform vec4 uGlyphBounds;\nattribute vec2 aUV;\nattribute vec4 aLineSegment;\nvarying vec4 vLineSegment;\nvarying vec2 vGlyphXY;\n\nvoid main() {\n vLineSegment = aLineSegment;\n vGlyphXY = mix(uGlyphBounds.xy, uGlyphBounds.zw, aUV);\n gl_Position = vec4(mix(vec2(-1.0), vec2(1.0), aUV), 0.0, 1.0);\n}\n",l.VERTEX_SHADER)),l.attachShader(f,b(l,"precision highp float;\nuniform vec4 uGlyphBounds;\nuniform float uMaxDistance;\nuniform float uExponent;\nvarying vec4 vLineSegment;\nvarying vec2 vGlyphXY;\n\nfloat absDistToSegment(vec2 point, vec2 lineA, vec2 lineB) {\n vec2 lineDir = lineB - lineA;\n float lenSq = dot(lineDir, lineDir);\n float t = lenSq == 0.0 ? 0.0 : clamp(dot(point - lineA, lineDir) / lenSq, 0.0, 1.0);\n vec2 linePt = lineA + t * lineDir;\n return distance(point, linePt);\n}\n\nbool isCrossing(vec2 point, vec2 lineA, vec2 lineB) {\n return (lineA.y > point.y != lineB.y > point.y) &&\n (point.x < (lineB.x - lineA.x) * (point.y - lineA.y) / (lineB.y - lineA.y) + lineA.x);\n}\n\nvoid main() {\n float dist = absDistToSegment(vGlyphXY, vLineSegment.xy, vLineSegment.zw);\n float val = pow(1.0 - clamp(dist / uMaxDistance, 0.0, 1.0), uExponent) * 0.5;\n bool crossing = isCrossing(vGlyphXY, vLineSegment.xy, vLineSegment.zw);\n gl_FragColor = vec4(val, 0.0, 0.0, crossing ? 1.0 / 256.0 : 0.0);\n}\n",l.FRAGMENT_SHADER)),l.linkProgram(f)),l.useProgram(f),A("aUV",2,l.STATIC_DRAW,0,p),A("aLineSegment",4,l.DYNAMIC_DRAW,1,h),E.apply(void 0,["uGlyphBounds"].concat(a)),E("uMaxDistance",i),E("uExponent",d),l.clear(l.COLOR_BUFFER_BIT),l.enable(l.BLEND),l.blendFunc(l.ONE,l.ONE),l.blendEquationSeparate(u.MAX_EXT,l.FUNC_ADD),c.drawArraysInstancedANGLE(l.TRIANGLES,0,3,h.length/4);var m=new Uint8Array(n*e*4);if(l.readPixels(0,0,n,e,l.RGBA,l.UNSIGNED_BYTE,m),l.isContextLost())throw x(),new Error("webgl context lost");for(var g=new Uint8Array(n*e),L=0,_=0;L<m.length;L+=4)g[_++]=m[L+3]%2?255-m[L]:m[L];return g}function S(){if(null===g){y=!0;var n=null;try{var e=[97,106,97,61,99,137,118,80,80,118,137,99,61,97,106,97],t=L(4,4,"M8,8L16,8L24,24L16,24Z",[0,0,32,32],24,1);(g=t&&e.length===t.length&&t.every((function(n,t){return n===e[t]})))||(n="bad trial run results")}catch(e){g=!1,n=e}n&&console.info("WebGL SDF generation not supported:",n),y=!1}return g}var _=Object.freeze({__proto__:null,generate:L,isSupported:S});return n.forEachPathCommand=t,n.generate=function(n,e,t,r,i,o){void 0===i&&(i=Math.max(r[2]-r[0],r[3]-r[1])/2),void 0===o&&(o=1);try{return L(n,e,t,r,i,o)}catch(l){return console.info("WebGL SDF generation failed, falling back to JS",l),a(n,e,t,r,i,o)}},n.javascript=d,n.pathToLineSegments=r,n.webgl=_,Object.defineProperty(n,"__esModule",{value:!0}),n}({});return n}})); |
{ | ||
"name": "webgl-sdf-generator", | ||
"version": "0.1.0", | ||
"version": "1.0.1", | ||
"description": "WebGL-accelerated signed distance field generation for 2D paths", | ||
@@ -5,0 +5,0 @@ "main": "dist/webgl-sdf-generator.js", |
# webgl-sdf-generator | ||
JavaScript functions that generate signed distance field (SDF) images for 2D paths such as text glyphs, accelerated | ||
using WebGL when available. | ||
This is a signed distance field (SDF) image generator for 2D paths such as font glyphs, for use in Web environments. It utilizes WebGL when possible for GPU-accelerated SDF generation. | ||
## Usage | ||
### Install it from npm: | ||
```shell | ||
npm install webgl-sdf-generator | ||
``` | ||
[![NPM](https://nodei.co/npm/webgl-sdf-generator.png?compact=true)](https://npmjs.org/package/webgl-sdf-generator) | ||
### Import and initialize: | ||
```js | ||
import initSDFGenerator from 'webgl-sdf-generator' | ||
// or: const initSDFGenerator = require('webgl-sdf-generator') | ||
const generator = initSDFGenerator() | ||
``` | ||
The `webgl-sdf-generator` package's only export is a factory function which you _must invoke_ to return an object which holds various methods for performing the generation. | ||
> _Why a factory function?_ The main reason is to ensure the entire module's code is wrapped within a single self-contained function with no closure dependencies. This enables that function to be stringified and passed into a web worker, for example. | ||
Note that each factory call will result in its own internal WebGL context, which may be useful in some rare cases, but usually you'll just want to call it once and share that single generator object. | ||
### Generate your SDF: | ||
```js | ||
const sdfImageData = generator.generate( | ||
64, // width | ||
64, // height | ||
'M0,0L50,25L25,50Z', // path | ||
[-5, -5, 55, 55], // viewBox | ||
25, // maxDistance | ||
1 // exponent | ||
) | ||
``` | ||
Let's break down those arguments... | ||
- **`width/height`** - The dimensions of the resulting image. | ||
- **`path`** - An SVG-like [path string](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#path_commands). Only the following path commands are currently supported: `M`, `L`, `Q`, `C`, and `Z`. | ||
- **`viewBox`** - The rectangle in the `path`'s coordinate system that will be covered by the output image. Specified as an array of `[left, top, right, bottom]`. You'll want to account for padding around the path shape in this rectangle. | ||
- **`maxDistance`** - The maximum distance that will be encoded in the distance field; this is the distance from the path's edge at which the SDF value will be `0` or `255`. | ||
- **`exponent`** - An optional exponent to apply to the SDF distance values as they get farther from the path's edge. This can be useful when `maxDistance` is large, to allow more precision near the path edge where it's more important and decreasing precision far away ([visualized here](https://www.desmos.com/calculator/uiaq5aqiam)). Whatever uses the SDF later on will need to invert the transformation to get useful distance values. Defaults to `1` for no curve. | ||
The return value is a `Uint8Array` of SDF image pixel values (single channel), where 127.5 is the "zero distance" aligning with path edges. Values below that are outside the path and values above it are inside the path. | ||
When you call `generator.generate(...)`, it will first attempt to build the SDF using WebGL; this is super fast because it is GPU-acclerated. This should work in most browsers, but if for whatever reason the proper WebGL support is not available or fails due to context loss then it will fall back to a slower JavaScript-based implementation. | ||
If you want more control over this fallback behavior, you can access the individual implementations directly: | ||
```js | ||
// Same arguments as the main generate(): | ||
const resultFromGL = generator.webgl.generate(...args) | ||
const resultFromJS = generator.javascript.generate(...args) | ||
``` | ||
The WebGL implementation also provides method to detect support if you want to test it beforehand: | ||
```js | ||
const webglSupported = generator.webgl.isSupported() | ||
``` | ||
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
75579
1399
1
74