UNPKG

graphics-ts

Version:

A porting of purescript-{canvas, free-canvas, drawing} featuring fp-ts

896 lines (895 loc) 25.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.renderTo = exports.bindWithContext = exports.bind = exports.withContext = exports.strokePath = exports.fillPath = exports.addColorStop = exports.translate = exports.transform = exports.strokeText = exports.strokeRect = exports.stroke = exports.setTransformMatrix = exports.setTransform = exports.setLineDash = exports.scale = exports.save = exports.rotate = exports.restore = exports.rect = exports.quadraticCurveTo = exports.putImageDataFull = exports.putImageData = exports.moveTo = exports.measureText = exports.lineTo = exports.isPointInStroke = exports.isPointInPath = exports.getTransform = exports.getLineDash = exports.getImageData = exports.fillText = exports.fillRect = exports.fill = exports.ellipse = exports.drawImageFull = exports.drawImageScale = exports.drawImage = exports.drawFocusIfNeeded = exports.createRadialGradient = exports.createPattern = exports.createLinearGradient = exports.createImageDataCopy = exports.createImageData = exports.closePath = exports.clip = exports.clearRect = exports.bezierCurveTo = exports.beginPath = exports.arcTo = exports.arc = exports.setTextBaseline = exports.getTextBaseline = exports.setTextAlign = exports.getTextAlign = exports.setStrokeStyle = exports.setShadowOffsetY = exports.setShadowOffsetX = exports.setShadowColor = exports.setShadowBlur = exports.setMiterLimit = exports.setLineWidth = exports.setLineJoin = exports.setLineDashOffset = exports.setLineCap = exports.setImageSmoothingEnabled = exports.setGlobalCompositeOperation = exports.setGlobalAlpha = exports.setFont = exports.getFont = exports.setFillStyle = exports.toDataURL = exports.setDimensions = exports.getDimensions = exports.setHeight = exports.getHeight = exports.setWidth = exports.getWidth = exports.getContext2D = exports.getCanvasElementById = exports.unsafeGetContext2D = exports.unsafeGetCanvasElementById = void 0; /** * The `Canvas` module contains all the functions necessary to interact with the HTML * Canvas API. `graphics-ts` wraps all canvas operations in an `IO<A>` to allow for * chaining multiple effectful calls to the HTML Canvas API. * * For example, taking the example of [drawing a triangle](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes) from the MDN Web Docs, the code * without `graphics-ts` looks like this. * * ```ts * const draw = () => { * var canvas = document.getElementById('canvas') * * if (canvas.getContext) { * var ctx = canvas.getContext('2d') * * ctx.beginPath(); * ctx.fillStyle = 'black' * ctx.moveTo(75, 50) * ctx.lineTo(100, 75) * ctx.lineTo(100, 25) * ctx.fill() * } * } * ``` * * With `graphics-ts`, the above code becomes * * ```ts * import { error } from 'fp-ts/lib/Console' * import { pipe } from 'fp-ts/lib/pipeable' * import * as R from 'fp-ts-contrib/lib/ReaderIO' * import * as C from 'graphics-ts/lib/Canvas' * import * as Color from 'graphics-ts/lib/Color' * import * as S from 'graphics-ts/lib/Shape' * * const canvasId = 'canvas' * * const triangle: C.Render<void> = C.fillPath( * pipe( * C.setFillStyle(pipe(Color.black, Color.toCss)), * R.chain(() => C.moveTo(S.point(75, 50))), * R.chain(() => C.lineTo(S.point(100, 75))), * R.chain(() => C.lineTo(S.point(100, 25))) * ) * ) * * C.renderTo(canvasId, () => error(`[ERROR]: Unable to find canvas with id ${canvasId}`))(triangle)() * ``` * * While this may seem somewhat verbose compared to its non-functional counterpart above, * the real power of the `Canvas` module is apparent when it is abstracted away by the * `Drawing` module. * * Adapted from https://github.com/purescript-contrib/purescript-canvas. * * @since 1.0.0 */ var R = require("fp-ts-contrib/lib/ReaderIO"); var IO = require("fp-ts/lib/IO"); var O = require("fp-ts/lib/Option"); var RA = require("fp-ts/lib/ReadonlyArray"); var Apply_1 = require("fp-ts/lib/Apply"); var function_1 = require("fp-ts/lib/function"); var pipeable_1 = require("fp-ts/lib/pipeable"); // ------------------------------------------------------------------------------------- // constructors // ------------------------------------------------------------------------------------- /** * **[UNSAFE]** Gets a canvas element by id. * * @category constructors * @since 1.0.0 */ exports.unsafeGetCanvasElementById = function (id) { return document.getElementById(id); }; /** * **[UNSAFE]** Gets the 2D graphics context for a canvas element. * * @category constructors * @since 1.0.0 */ exports.unsafeGetContext2D = function (c) { return c.getContext('2d'); }; /** * Gets an canvas element by id, or `None` if the element does not exist or is not an * instance of `HTMLCanvasElement`. * * @category constructors * @since 1.0.0 */ exports.getCanvasElementById = function (id) { return function () { var canvas = exports.unsafeGetCanvasElementById(id); return canvas instanceof HTMLCanvasElement ? O.some(canvas) : O.none; }; }; // ------------------------------------------------------------------------------------- // combinators // ------------------------------------------------------------------------------------- /** * Gets the 2D graphics context for a canvas element. * * @category combinators * @since 1.0.0 */ exports.getContext2D = function (c) { return IO.of(exports.unsafeGetContext2D(c)); }; /** * Gets the canvas width in pixels. * * @category combinators * @since 1.0.0 */ exports.getWidth = function (c) { return function () { return c.width; }; }; /** * Sets the width of the canvas in pixels. * * @category combinators * @since 1.0.0 */ exports.setWidth = function (w) { return function (c) { return function () { c.width = w; return c; }; }; }; /** * Gets the canvas height in pixels. * * @category combinators * @since 1.0.0 */ exports.getHeight = function (c) { return function () { return c.height; }; }; /** * Sets the height of the canvas in pixels. * * @category combinators * @since 1.0.0 */ exports.setHeight = function (h) { return function (c) { return function () { c.height = h; return c; }; }; }; /** * Gets the dimensions of the canvas in pixels. * * @category combinators * @since 1.0.0 */ exports.getDimensions = function (c) { return Apply_1.sequenceS(IO.io)({ height: exports.getHeight(c), width: exports.getWidth(c) }); }; /** * Sets the dimensions of the canvas in pixels. * * @category combinators * @since 1.0.0 */ exports.setDimensions = function (d) { return pipeable_1.pipe(exports.setWidth(d.width), R.chain(function () { return exports.setHeight(d.height); })); }; /** * Create a data URL for the canvas. * * @category combinators * @since 1.0.0 */ exports.toDataURL = function (c) { return function () { return c.toDataURL(); }; }; /** * Sets the current fill style for the canvas context. * * @category combinators * @since 1.0.0 */ exports.setFillStyle = function (s) { return function (ctx) { return function () { ctx.fillStyle = s; return ctx; }; }; }; /** * Gets the current font. * * @category combinators * @since 1.0.0 */ exports.getFont = function (ctx) { return function () { return ctx.font; }; }; /** * Sets the current font. * * @category combinators * @since 1.0.0 */ exports.setFont = function (f) { return function (ctx) { return function () { ctx.font = f; return ctx; }; }; }; /** * Sets the current global alpha for the canvas context. * * @category combinators * @since 1.0.0 */ exports.setGlobalAlpha = function (a) { return function (ctx) { return function () { ctx.globalAlpha = a; return ctx; }; }; }; /** * Sets the current global composite operation type for the canvas context. * * @category combinators * @since 1.0.0 */ exports.setGlobalCompositeOperation = function (gco) { return function (ctx) { return function () { ctx.globalCompositeOperation = gco; return ctx; }; }; }; /** * Sets the current image smoothing property for the canvas context. Determines whether scaled images are smoothed * (`true`, default) or not (`false`). * * @category combinators * @since 1.0.0 */ exports.setImageSmoothingEnabled = function (v) { return function (ctx) { return function () { ctx.imageSmoothingEnabled = v; return ctx; }; }; }; /** * Sets the current line cap type for the canvas context. * * @category combinators * @since 1.0.0 */ exports.setLineCap = function (c) { return function (ctx) { return function () { ctx.lineCap = c; return ctx; }; }; }; /** * Sets the current line dash offset, or "phase", for the canvas context. * * @category combinators * @since 1.0.0 */ exports.setLineDashOffset = function (o) { return function (ctx) { return function () { ctx.lineDashOffset = o; return ctx; }; }; }; /** * Sets the current line join type for the canvas context. * * @category combinators * @since 1.0.0 */ exports.setLineJoin = function (j) { return function (ctx) { return function () { ctx.lineJoin = j; return ctx; }; }; }; /** * Sets the current line width for the canvas context in pixels. * * @category combinators * @since 1.0.0 */ exports.setLineWidth = function (w) { return function (ctx) { return function () { ctx.lineWidth = w; return ctx; }; }; }; /** * Sets the current miter limit for the canvas context. * * @category combinators * @since 1.0.0 */ exports.setMiterLimit = function (l) { return function (ctx) { return function () { ctx.miterLimit = l; return ctx; }; }; }; /** * Sets the current shadow blur radius for the canvas context. * * @category combinators * @since 1.0.0 */ exports.setShadowBlur = function (b) { return function (ctx) { return function () { ctx.shadowBlur = b; return ctx; }; }; }; /** * Sets the current shadow color for the canvas context. * * @category combinators * @since 1.0.0 */ exports.setShadowColor = function (c) { return function (ctx) { return function () { ctx.shadowColor = c; return ctx; }; }; }; /** * Sets the current shadow x-offset for the canvas context. * * @category combinators * @since 1.0.0 */ exports.setShadowOffsetX = function (ox) { return function (ctx) { return function () { ctx.shadowOffsetX = ox; return ctx; }; }; }; /** * Sets the current shadow y-offset for the canvas context. * * @category combinators * @since 1.0.0 */ exports.setShadowOffsetY = function (oy) { return function (ctx) { return function () { ctx.shadowOffsetY = oy; return ctx; }; }; }; /** * Sets the current stroke style for the canvas context. * * @category combinators * @since 1.0.0 */ exports.setStrokeStyle = function (s) { return function (ctx) { return function () { ctx.strokeStyle = s; return ctx; }; }; }; /** * Gets the current text alignment. * * @category combinators * @since 1.0.0 */ exports.getTextAlign = function (ctx) { return function () { return ctx.textAlign; }; }; /** * Sets the current text alignment. * * @category combinators * @since 1.0.0 */ exports.setTextAlign = function (ta) { return function (ctx) { return function () { ctx.textAlign = ta; return ctx; }; }; }; /** * Gets the current text baseline. * * @category combinators * @since 1.0.0 */ exports.getTextBaseline = function (ctx) { return function () { return ctx.textBaseline; }; }; /** * Sets the current text baseline. * * @category combinators * @since 1.0.0 */ exports.setTextBaseline = function (tb) { return function (ctx) { return function () { ctx.textBaseline = tb; return ctx; }; }; }; /** * Render an arc. * * @category combinators * @since 1.0.0 */ exports.arc = function (a) { return function (ctx) { return function () { ctx.arc(a.x, a.y, a.r, a.start, a.end, a.anticlockwise); return ctx; }; }; }; /** * Render an arc that is automatically connected to the path's latest point. * * @category combinators * @since 1.0.0 */ exports.arcTo = function (x1, y1, x2, y2, r) { return function (ctx) { return function () { ctx.arcTo(x1, y1, x2, y2, r); return ctx; }; }; }; /** * Begin a path on the canvas. * * @category combinators * @since 1.0.0 */ exports.beginPath = function (ctx) { return function () { ctx.beginPath(); return ctx; }; }; /** * Draw a cubic Bézier curve. * * @category combinators * @since 1.0.0 */ exports.bezierCurveTo = function (cpx1, cpy1, cpx2, cpy2, x, y) { return function (ctx) { return function () { ctx.bezierCurveTo(cpx1, cpy1, cpx2, cpy2, x, y); return ctx; }; }; }; /** * Set the pixels in the specified rectangle back to transparent black. * * @category combinators * @since 1.0.0 */ exports.clearRect = function (r) { return function (ctx) { return function () { ctx.clearRect(r.x, r.y, r.width, r.height); return ctx; }; }; }; /** * Clip the current path on the canvas. * * @category combinators * @since 1.0.0 */ exports.clip = function (f, p) { return function (ctx) { return function () { if (typeof p !== 'undefined') { ctx.clip(p, f); } else if (typeof f !== 'undefined') { ctx.clip(f); } else { ctx.clip(); } return ctx; }; }; }; /** * Closes the current canvas path. * * @category combinators * @since 1.0.0 */ exports.closePath = function (ctx) { return function () { ctx.closePath(); return ctx; }; }; /** * Gets `ImageData` for the specified rectangle. * * @category combinators * @since 1.0.0 */ exports.createImageData = function (sw, sh) { return function (ctx) { return function () { return ctx.createImageData(sw, sh); }; }; }; /** * Creates a copy of an existing `ImageData` object. * * @category combinators * @since 1.0.0 */ exports.createImageDataCopy = function (data) { return function (ctx) { return function () { return ctx.createImageData(data); }; }; }; /** * Creates a linear `CanvasGradient` object. * * @category combinators * @since 1.0.0 */ exports.createLinearGradient = function (x0, y0, x1, y1) { return function (ctx) { return function () { return ctx.createLinearGradient(x0, y0, x1, y1); }; }; }; /** * Creates a new canvas pattern (repeatable image). * * @category combinators * @since 1.0.0 */ exports.createPattern = function (s, r) { return function (ctx) { return function () { return O.fromNullable(ctx.createPattern(s, r)); }; }; }; /** * Creates a radial `CanvasGradient` object. * * @category combinators * @since 1.0.0 */ exports.createRadialGradient = function (x0, y0, r0, x1, y1, r1) { return function (ctx) { return function () { return ctx.createRadialGradient(x0, y0, r0, x1, y1, r1); }; }; }; /** * Draws a focus ring around the current or given path, if the specified element is focused. * * @category combinators * @since 1.0.0 */ exports.drawFocusIfNeeded = function (el, p) { return function (ctx) { return function () { if (typeof p !== 'undefined') { ctx.drawFocusIfNeeded(p, el); } else { ctx.drawFocusIfNeeded(el); } return ctx; }; }; }; /** * Render an image. * * @category combinators * @since 1.0.0 */ exports.drawImage = function (s, ox, oy) { return function (ctx) { return function () { ctx.drawImage(s, ox, oy); return ctx; }; }; }; /** * Draws an image to the canvas. * * @category combinators * @since 1.0.0 */ exports.drawImageScale = function (s, ox, oy, w, h) { return function (ctx) { return function () { ctx.drawImage(s, ox, oy, w, h); return ctx; }; }; }; /** * Draws an image to the canvas. * * @category combinators * @since 1.0.0 */ exports.drawImageFull = function (s, ox, oy, w, h, cox, coy, ciw, cih) { return function (ctx) { return function () { ctx.drawImage(s, ox, oy, w, h, cox, coy, ciw, cih); return ctx; }; }; }; /** * Render an ellipse. * * @category combinators * @since 1.0.0 */ exports.ellipse = function (e) { return function (ctx) { return function () { ctx.ellipse(e.x, e.y, e.rx, e.ry, e.rotation, e.start, e.end, e.anticlockwise); return ctx; }; }; }; /** * Fill the current path on the canvas. * * @category combinators * @since 1.0.0 */ exports.fill = function (f, p) { return function (ctx) { return function () { if (typeof p !== 'undefined') { ctx.fill(p, f); } else if (typeof f !== 'undefined') { ctx.fill(f); } else { ctx.fill(); } return ctx; }; }; }; /** * Render a filled rectangle. * * @category combinators * @since 1.0.0 */ exports.fillRect = function (r) { return function (ctx) { return function () { ctx.fillRect(r.x, r.y, r.width, r.height); return ctx; }; }; }; /** * Render filled text. * * @category combinators * @since 1.0.0 */ exports.fillText = function (t, x, y, mw) { return function (ctx) { return function () { if (typeof mw !== 'undefined') { ctx.fillText(t, x, y, mw); } else { ctx.fillText(t, x, y); } return ctx; }; }; }; /** * Gets the image data for the specified portion of the canvas. * * @category combinators * @since 1.0.0 */ exports.getImageData = function (r) { return function (ctx) { return function () { return ctx.getImageData(r.x, r.y, r.width, r.height); }; }; }; /** * Gets the current line dash pattern for the canvas context. * * @category combinators * @since 1.0.0 */ exports.getLineDash = function (ctx) { return function () { return RA.fromArray(ctx.getLineDash()); }; }; /** * Gets the current transformation matrix being applied to the canvas context. * * @category combinators * @since 1.0.0 */ exports.getTransform = function (ctx) { return function () { return ctx.getTransform(); }; }; /** * Determines if the specified point is contained in the current path. * * @category combinators * @since 1.0.0 */ exports.isPointInPath = function (p, rule, path) { return function (ctx) { return function () { if (typeof path !== 'undefined') { return ctx.isPointInPath(path, p.x, p.y, rule); } else { return typeof rule !== 'undefined' ? ctx.isPointInPath(p.x, p.y, rule) : ctx.isPointInPath(p.x, p.y); } }; }; }; /** * Determines if the specified point is inside the area contained by the stroking of a path. * * @category combinators * @since 1.0.0 */ exports.isPointInStroke = function (p, path) { return function (ctx) { return function () { return typeof path !== 'undefined' ? ctx.isPointInStroke(path, p.x, p.y) : ctx.isPointInStroke(p.x, p.y); }; }; }; /** * Move the canvas path to the specified point while drawing a line segment. * * @category combinators * @since 1.0.0 */ exports.lineTo = function (p) { return function (ctx) { return function () { ctx.lineTo(p.x, p.y); return ctx; }; }; }; /** * Get the text measurements for the specified text. * * @category combinators * @since 1.0.0 */ exports.measureText = function (t) { return function (ctx) { return function () { return ctx.measureText(t); }; }; }; /** * Move the canvas path to the specified point without drawing a line segment. * * @category combinators * @since 1.0.0 */ exports.moveTo = function (p) { return function (ctx) { return function () { ctx.moveTo(p.x, p.y); return ctx; }; }; }; /** * Sets the image data for the specified portion of the canvas. * * @category combinators * @since 1.0.0 */ exports.putImageData = function (data, dx, dy) { return function (ctx) { return function () { ctx.putImageData(data, dx, dy); return ctx; }; }; }; /** * Sets the image data for the specified portion of the canvas. * * @category combinators * @since 1.0.0 */ exports.putImageDataFull = function (data, dx, dy, dirtyX, dirtyY, dirtyW, dirtyH) { return function (ctx) { return function () { ctx.putImageData(data, dx, dy, dirtyX, dirtyY, dirtyW, dirtyH); return ctx; }; }; }; /** * Draws a quadratic Bézier curve. * * @category combinators * @since 1.0.0 */ exports.quadraticCurveTo = function (cpx, cpy, x, y) { return function (ctx) { return function () { ctx.quadraticCurveTo(cpx, cpy, x, y); return ctx; }; }; }; /** * Render a rectangle. * * @category combinators * @since 1.0.0 */ exports.rect = function (r) { return function (ctx) { return function () { ctx.rect(r.x, r.y, r.width, r.height); return ctx; }; }; }; /** * Restore the previous canvas context. * * @category combinators * @since 1.0.0 */ exports.restore = function (ctx) { return function () { ctx.restore(); return ctx; }; }; /** * Apply rotation to the current canvas context transform. * * @category combinators * @since 1.0.0 */ exports.rotate = function (a) { return function (ctx) { return function () { ctx.rotate(a); return ctx; }; }; }; /** * Save the current canvas context. * * @category combinators * @since 1.0.0 */ exports.save = function (ctx) { return function () { ctx.save(); return ctx; }; }; /** * Apply scale to the current canvas context transform. * * @category combinators * @since 1.0.0 */ exports.scale = function (x, y) { return function (ctx) { return function () { ctx.scale(x, y); return ctx; }; }; }; /** * Sets the current line dash pattern used when stroking lines. * * @category combinators * @since 1.0.0 */ exports.setLineDash = function (ss) { return function (ctx) { return function () { ctx.setLineDash(RA.toArray(ss)); return ctx; }; }; }; /** * Resets the current transformation to the identity matrix, and then applies the transform specified * to the current canvas context. * * @category combinators * @since 1.0.0 */ exports.setTransform = function (a, b, c, d, e, f) { return function (ctx) { return function () { ctx.setTransform(a, b, c, d, e, f); return ctx; }; }; }; /** * Resets the current transformation to the identity matrix, and then applies the transform specified * to the current canvas context. * * @category combinators * @since 1.0.0 */ exports.setTransformMatrix = function (matrix) { return function (ctx) { return function () { ctx.setTransform(matrix); return ctx; }; }; }; /** * Stroke the current path on the canvas. * * @category combinators * @since 1.0.0 */ exports.stroke = function (p) { return function (ctx) { return function () { if (typeof p !== 'undefined') { ctx.stroke(p); } else { ctx.stroke(); } return ctx; }; }; }; /** * Render a stroked rectangle. * * @category combinators * @since 1.0.0 */ exports.strokeRect = function (r) { return function (ctx) { return function () { ctx.strokeRect(r.x, r.y, r.width, r.height); return ctx; }; }; }; /** * Render stroked text. * * @category combinators * @since 1.0.0 */ exports.strokeText = function (t, x, y, mw) { return function (ctx) { return function () { if (typeof mw !== 'undefined') { ctx.strokeText(t, x, y, mw); } else { ctx.strokeText(t, x, y); } return ctx; }; }; }; /** * Apply the specified transformation matrix to the canvas context. * * @category combinators * @since 1.0.0 */ exports.transform = function (m11, m12, m21, m22, m31, m32) { return function (ctx) { return function () { ctx.transform(m11, m12, m21, m22, m31, m32); return ctx; }; }; }; /** * Translate the current canvas context transform. * * @category combinators * @since 1.0.0 */ exports.translate = function (x, y) { return function (ctx) { return function () { ctx.translate(x, y); return ctx; }; }; }; /** * Add a single color stop to a `CanvasGradient` object. * * @category combinators * @since 1.0.0 */ exports.addColorStop = function (o, c) { return function (g) { return function () { g.addColorStop(o, c); return g; }; }; }; /** * Convenience function for drawing a filled path. * * @category combinators * @since 1.0.0 */ exports.fillPath = function (f) { return pipeable_1.pipe(exports.beginPath, R.chain(function () { return f; }), R.chainFirst(function () { return exports.fill(); })); }; /** * Convenience function for drawing a stroked path. * * @category combinators * @since 1.0.0 */ exports.strokePath = function (f) { return pipeable_1.pipe(exports.beginPath, R.chain(function () { return f; }), R.chainFirst(function () { return exports.stroke(); })); }; /** * A convenience function which allows for running an action while preserving the existing * canvas context. * * @category combinators * @since 1.0.0 */ exports.withContext = function (f) { return pipeable_1.pipe(exports.save, R.chain(function () { return f; }), R.chainFirst(function () { return exports.restore; })); }; // TODO: remove in version 2.0.0 /** * Binds an event handler to the canvas element. * * @deprecated since 1.1.0 * @category combinators * @since 1.0.0 */ exports.bind = function (t, f) { return function (c) { return function () { c.addEventListener(t, f); return c; }; }; }; // TODO: rename in version 2.0.0 /** * Binds an event handler to the canvas element. * * @category combinators * @since 1.1.0 */ exports.bindWithContext = function (type, f) { return function (ctx) { return function () { ctx.canvas.addEventListener(type, function (e) { return f(e)(ctx)(); }); return ctx; }; }; }; // ------------------------------------------------------------------------------------- // utils // ------------------------------------------------------------------------------------- /** * Executes a `Render` effect for a canvas with the specified `canvasId`, or `onCanvasNotFound()` if a canvas with * the specified `canvasId` does not exist. * * @since 1.0.0 */ exports.renderTo = function (canvasId, onCanvasNotFound) { return function (r) { return pipeable_1.pipe(exports.getCanvasElementById(canvasId), IO.chain(O.fold(onCanvasNotFound, function_1.flow(exports.getContext2D, IO.chain(r))))); }; };