/*
 * Copyright 2018 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "include/android/SkAnimatedImage.h"
#include "include/codec/SkAndroidCodec.h"
#include "include/codec/SkCodec.h"
#include "include/codec/SkEncodedImageFormat.h"
#include "include/core/SkBBHFactory.h"
#include "include/core/SkBlendMode.h"
#include "include/core/SkBlender.h"
#include "include/core/SkBlurTypes.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkColorFilter.h"
#include "include/core/SkColorSpace.h"
#include "include/core/SkData.h"
#include "include/core/SkImage.h"
#include "include/core/SkImageFilter.h"
#include "include/core/SkImageGenerator.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkM44.h"
#include "include/core/SkMaskFilter.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPath.h"
#include "include/core/SkPathEffect.h"
#include "include/core/SkPathMeasure.h"
#include "include/core/SkPathUtils.h"
#include "include/core/SkPicture.h"
#include "include/core/SkPictureRecorder.h"
#include "include/core/SkPoint3.h"
#include "include/core/SkRRect.h"
#include "include/core/SkSamplingOptions.h"
#include "include/core/SkScalar.h"
#include "include/core/SkSerialProcs.h"
#include "include/core/SkShader.h"
#include "include/core/SkStream.h"
#include "include/core/SkString.h"
#include "include/core/SkStrokeRec.h"
#include "include/core/SkSurface.h"
#include "include/core/SkTextBlob.h"
#include "include/core/SkTypeface.h"
#include "include/core/SkTypes.h"
#include "include/core/SkVertices.h"
#include "include/effects/Sk1DPathEffect.h"
#include "include/effects/Sk2DPathEffect.h"
#include "include/effects/SkCornerPathEffect.h"
#include "include/effects/SkDashPathEffect.h"
#include "include/effects/SkDiscretePathEffect.h"
#include "include/effects/SkGradientShader.h"
#include "include/effects/SkImageFilters.h"
#include "include/effects/SkLumaColorFilter.h"
#include "include/effects/SkPerlinNoiseShader.h"
#include "include/effects/SkRuntimeEffect.h"
#include "include/effects/SkTrimPathEffect.h"
#include "include/encode/SkJpegEncoder.h"
#include "include/encode/SkPngEncoder.h"
#include "include/encode/SkWebpEncoder.h"
#include "include/private/base/SkOnce.h"
#include "include/utils/SkParsePath.h"
#include "include/utils/SkShadowUtils.h"
#include "src/base/SkFloatBits.h"
#include "src/core/SkPathPriv.h"
#include "src/core/SkPathRaw.h"
#include "src/core/SkResourceCache.h"
#include "src/image/SkImage_Base.h"

#include <emscripten.h>
#include <emscripten/bind.h>
#include <emscripten/html5.h>
#include "modules/canvaskit/WasmCommon.h"

#if defined(CK_ENABLE_WEBGL) || defined(CK_ENABLE_WEBGPU)
#define ENABLE_GPU
#endif

#ifdef ENABLE_GPU
#include "include/gpu/GpuTypes.h"
#include "include/gpu/ganesh/GrDirectContext.h"
#include "include/gpu/ganesh/GrExternalTextureGenerator.h"
#include "include/gpu/ganesh/SkImageGanesh.h"
#include "include/gpu/ganesh/SkSurfaceGanesh.h"
#include "src/gpu/ganesh/GrCaps.h"
#endif  // ENABLE_GPU

#ifdef CK_ENABLE_WEBGL
#include "include/gpu/ganesh/GrBackendSurface.h"
#include "include/gpu/ganesh/GrTypes.h"
#include "include/gpu/ganesh/gl/GrGLBackendSurface.h"
#include "include/gpu/ganesh/gl/GrGLDirectContext.h"
#include "include/gpu/ganesh/gl/GrGLInterface.h"
#include "include/gpu/ganesh/gl/GrGLMakeWebGLInterface.h"
#include "include/gpu/ganesh/gl/GrGLTypes.h"
#include "src/gpu/RefCntedCallback.h"
#include "src/gpu/ganesh/GrProxyProvider.h"
#include "src/gpu/ganesh/GrRecordingContextPriv.h"
#include "src/gpu/ganesh/gl/GrGLDefines.h"

#include <GLES2/gl2.h>
#endif  // CK_ENABLE_WEBGL

#ifdef CK_ENABLE_WEBGPU
#include <emscripten/html5_webgpu.h>
#include <webgpu/webgpu.h>
#include <webgpu/webgpu_cpp.h>
#endif  // CK_ENABLE_WEBGPU

#ifndef CK_NO_FONTS
#include "include/core/SkFont.h"
#include "include/core/SkFontMetrics.h"
#include "include/core/SkFontMgr.h"
#include "include/core/SkFontTypes.h"
#include "src/ports/SkTypeface_FreeType.h"
#ifdef CK_INCLUDE_PARAGRAPH
#include "modules/skparagraph/include/Paragraph.h"
#endif  // CK_INCLUDE_PARAGRAPH
#endif  // CK_NO_FONTS

#ifdef CK_INCLUDE_PATHOPS
#include "include/pathops/SkPathOps.h"
#endif

#if defined(CK_INCLUDE_RUNTIME_EFFECT)
#include "include/sksl/SkSLDebugTrace.h"
#include "src/sksl/SkSLCompiler.h"
#include "src/sksl/tracing/SkSLDebugTracePriv.h"
#if defined(CK_DEBUG_TRACE_JSON)
#include "tools/sksltrace/SkSLTraceUtils.h"
#endif
#endif

#ifndef CK_NO_FONTS
#include "include/ports/SkFontMgr_data.h"
#endif

#if defined(CK_EMBED_FONT)
struct SkEmbeddedResource {
    const uint8_t* data;
    size_t size;
};
struct SkEmbeddedResourceHeader {
    const SkEmbeddedResource* entries;
    int count;
};
extern "C" const SkEmbeddedResourceHeader SK_EMBEDDED_FONTS;
#endif

#if defined(GPU_TEST_UTILS)
#error "This define should not be set, as it brings in test-only things and bloats codesize."
#endif

#if defined(SK_CODEC_DECODES_BMP)
#include "include/codec/SkBmpDecoder.h"
#endif
#if defined(SK_CODEC_DECODES_GIF)
#include "include/codec/SkGifDecoder.h"
#endif
#if defined(SK_CODEC_DECODES_ICO)
#include "include/codec/SkIcoDecoder.h"
#endif
#if defined(SK_CODEC_DECODES_JPEG)
#include "include/codec/SkJpegDecoder.h"
#endif
#if defined(SK_CODEC_DECODES_PNG)
#include "include/codec/SkPngDecoder.h"
#endif
#if defined(SK_CODEC_DECODES_WBMP)
#include "include/codec/SkWbmpDecoder.h"
#endif
#if defined(SK_CODEC_DECODES_WEBP)
#include "include/codec/SkWebpDecoder.h"
#endif

// We'd like clients to be able to compile in as many or few codecs as they want (e.g. codesize)
std::unique_ptr<SkCodec> DecodeImageData(sk_sp<SkData> data) {
    if (data == nullptr) {
        return nullptr;
    }
    // These codecs are arbitrarily ordered in alphabetical order.
#if defined(SK_CODEC_DECODES_BMP)
    if (SkBmpDecoder::IsBmp(data->data(), data->size())) {
        return SkBmpDecoder::Decode(data, nullptr);
    }
#endif
#if defined(SK_CODEC_DECODES_GIF)
    if (SkGifDecoder::IsGif(data->data(), data->size())) {
        return SkGifDecoder::Decode(data, nullptr);
    }
#endif
#if defined(SK_CODEC_DECODES_ICO)
    if (SkIcoDecoder::IsIco(data->data(), data->size())) {
        return SkIcoDecoder::Decode(data, nullptr);
    }
#endif
#if defined(SK_CODEC_DECODES_JPEG)
    if (SkJpegDecoder::IsJpeg(data->data(), data->size())) {
        return SkJpegDecoder::Decode(data, nullptr);
    }
#endif
#if defined(SK_CODEC_DECODES_PNG)
    if (SkPngDecoder::IsPng(data->data(), data->size())) {
        return SkPngDecoder::Decode(data, nullptr);
    }
#endif
#if defined(SK_CODEC_DECODES_WBMP)
    if (SkWbmpDecoder::IsWbmp(data->data(), data->size())) {
        return SkWbmpDecoder::Decode(data, nullptr);
    }
#endif
#if defined(SK_CODEC_DECODES_WEBP)
    if (SkWebpDecoder::IsWebp(data->data(), data->size())) {
        return SkWebpDecoder::Decode(data, nullptr);
    }
#endif
    return nullptr;
}

struct OptionalMatrix : SkMatrix {
    OptionalMatrix(WASMPointerF32 mPtr) {
        if (mPtr) {
            const float* nineMatrixValues = reinterpret_cast<const float*>(mPtr);
            this->set9(nineMatrixValues);
        }
    }
};

SkColor4f ptrToSkColor4f(WASMPointerF32 cPtr) {
    float* fourFloats = reinterpret_cast<float*>(cPtr);
    SkColor4f color;
    memcpy(&color, fourFloats, 4 * sizeof(float));
    return color;
}

SkRRect ptrToSkRRect(WASMPointerF32 fPtr) {
    // In order, these floats should be 4 floats for the rectangle
    // (left, top, right, bottom) and then 8 floats for the radii
    // (upper left, upper right, lower right, lower left).
    const float* twelveFloats = reinterpret_cast<const float*>(fPtr);
    const SkRect rect = reinterpret_cast<const SkRect*>(twelveFloats)[0];
    const SkVector* radiiValues = reinterpret_cast<const SkVector*>(twelveFloats + 4);

    SkRRect rr;
    rr.setRectRadii(rect, radiiValues);
    return rr;
}

// Surface creation structs and helpers
struct SimpleImageInfo {
    int width;
    int height;
    SkColorType colorType;
    SkAlphaType alphaType;
    sk_sp<SkColorSpace> colorSpace;
};

SkImageInfo toSkImageInfo(const SimpleImageInfo& sii) {
    return SkImageInfo::Make(sii.width,
                             sii.height,
                             sii.colorType,
                             sii.alphaType,
                             sii.colorSpace ? sii.colorSpace : SkColorSpace::MakeSRGB());
}

#ifdef CK_ENABLE_WEBGL

// Set the pixel format based on the colortype.
// These degrees of freedom are removed from canvaskit only to keep the interface simpler.
struct ColorSettings {
    ColorSettings(sk_sp<SkColorSpace> colorSpace) {
        if (colorSpace == nullptr || colorSpace->isSRGB()) {
            colorType = kRGBA_8888_SkColorType;
            pixFormat = GR_GL_RGBA8;
        } else {
            colorType = kRGBA_F16_SkColorType;
            pixFormat = GR_GL_RGBA16F;
        }
    }
    SkColorType colorType;
    GrGLenum pixFormat;
};

sk_sp<GrDirectContext> MakeGrContext() {
    // We assume that any calls we make to GL for the remainder of this function will go to the
    // desired WebGL Context.
    // setup interface.
    auto interface = GrGLInterfaces::MakeWebGL();
    // setup context
    return GrDirectContexts::MakeGL(interface);
}

sk_sp<SkSurface> MakeOnScreenGLSurface(sk_sp<GrDirectContext> dContext,
                                       int width,
                                       int height,
                                       sk_sp<SkColorSpace> colorSpace,
                                       int sampleCnt,
                                       int stencil) {
    // WebGL should already be clearing the color and stencil buffers, but do it again here to
    // ensure Skia receives them in the expected state.
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glClearColor(0, 0, 0, 0);
    glClearStencil(0);
    glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    dContext->resetContext(kRenderTarget_GrGLBackendState | kMisc_GrGLBackendState);

    // The on-screen canvas is FBO 0. Wrap it in a Skia render target so Skia can render to it.
    GrGLFramebufferInfo info;
    info.fFBOID = 0;

    if (!colorSpace) {
        colorSpace = SkColorSpace::MakeSRGB();
    }

    const auto colorSettings = ColorSettings(colorSpace);
    info.fFormat = colorSettings.pixFormat;
    auto target = GrBackendRenderTargets::MakeGL(width, height, sampleCnt, stencil, info);
    sk_sp<SkSurface> surface(SkSurfaces::WrapBackendRenderTarget(dContext.get(),
                                                                 target,
                                                                 kBottomLeft_GrSurfaceOrigin,
                                                                 colorSettings.colorType,
                                                                 colorSpace,
                                                                 nullptr));
    return surface;
}

sk_sp<SkSurface> MakeOnScreenGLSurface(sk_sp<GrDirectContext> dContext,
                                       int width,
                                       int height,
                                       sk_sp<SkColorSpace> colorSpace) {
    GrGLint sampleCnt;
    glGetIntegerv(GL_SAMPLES, &sampleCnt);

    GrGLint stencil;
    glGetIntegerv(GL_STENCIL_BITS, &stencil);

    return MakeOnScreenGLSurface(dContext, width, height, colorSpace, sampleCnt, stencil);
}

sk_sp<SkSurface> MakeRenderTarget(sk_sp<GrDirectContext> dContext, int width, int height) {
    SkImageInfo info = SkImageInfo::MakeN32(
            width, height, SkAlphaType::kPremul_SkAlphaType, SkColorSpace::MakeSRGB());

    sk_sp<SkSurface> surface(SkSurfaces::RenderTarget(dContext.get(),
                                                      skgpu::Budgeted::kYes,
                                                      info,
                                                      0,
                                                      kBottomLeft_GrSurfaceOrigin,
                                                      nullptr,
                                                      true));
    return surface;
}

sk_sp<SkSurface> MakeRenderTarget(sk_sp<GrDirectContext> dContext, SimpleImageInfo sii) {
    sk_sp<SkSurface> surface(SkSurfaces::RenderTarget(dContext.get(),
                                                      skgpu::Budgeted::kYes,
                                                      toSkImageInfo(sii),
                                                      0,
                                                      kBottomLeft_GrSurfaceOrigin,
                                                      nullptr,
                                                      true));
    return surface;
}
#endif  // CK_ENABLE_WEBGL

#ifdef CK_ENABLE_WEBGPU

sk_sp<GrDirectContext> MakeGrContext() {
    GrContextOptions options;
    wgpu::Device device = wgpu::Device::Acquire(emscripten_webgpu_get_device());
    return GrDirectContext::MakeDawn(device, options);
}

sk_sp<SkSurface> MakeGPUTextureSurface(sk_sp<GrDirectContext> dContext,
                                       uint32_t textureHandle,
                                       uint32_t textureFormat,
                                       int width,
                                       int height,
                                       sk_sp<SkColorSpace> colorSpace) {
    if (!colorSpace) {
        colorSpace = SkColorSpace::MakeSRGB();
    }

    wgpu::TextureFormat format = static_cast<wgpu::TextureFormat>(textureFormat);
    wgpu::Texture texture(emscripten_webgpu_import_texture(textureHandle));
    emscripten_webgpu_release_js_handle(textureHandle);

    // GrDawnRenderTargetInfo currently only supports a 1-mip TextureView.
    constexpr uint32_t mipLevelCount = 1;
    constexpr uint32_t sampleCount = 1;

    GrDawnTextureInfo info;
    info.fTexture = texture;
    info.fFormat = format;
    info.fLevelCount = mipLevelCount;

    GrBackendTexture target(width, height, info);
    return SkSurfaces::WrapBackendTexture(
            dContext.get(),
            target,
            kTopLeft_GrSurfaceOrigin,
            sampleCount,
            colorSpace->isSRGB() ? kRGBA_8888_SkColorType : kRGBA_F16_SkColorType,
            colorSpace,
            nullptr);
}

bool ReplaceBackendTexture(
        SkSurface& surface, uint32_t textureHandle, uint32_t textureFormat, int width, int height) {
    wgpu::TextureFormat format = static_cast<wgpu::TextureFormat>(textureFormat);
    wgpu::Texture texture(emscripten_webgpu_import_texture(textureHandle));
    emscripten_webgpu_release_js_handle(textureHandle);

    GrDawnTextureInfo info;
    info.fTexture = texture;
    info.fFormat = format;
    info.fLevelCount = 1;

    // Use kDiscard_ContentChangeMode to discard the contents of the old backing texture. This not
    // only avoids an unnecessary blit, we also don't support copying the contents of a swapchain
    // texture due to the default GPUCanvasConfiguration usage bits we used when configuring the
    // GPUCanvasContext in JS.
    //
    // The default usage bits only contain GPUTextureUsage.RENDER_ATTACHMENT. To support a copy we
    // would need to also set GPUTextureUsage.TEXTURE_BINDING (to sample it in a shader) or
    // GPUTextureUsage.COPY_SRC (for a copy command).
    //
    // See https://www.w3.org/TR/webgpu/#namespacedef-gputextureusage and
    // https://www.w3.org/TR/webgpu/#dictdef-gpucanvasconfiguration.
    GrBackendTexture target(width, height, info);
    return surface.replaceBackendTexture(
            target, kTopLeft_GrSurfaceOrigin, SkSurface::kDiscard_ContentChangeMode);
}

#endif  // CK_ENABLE_WEBGPU

//========================================================================================
// Path things
//========================================================================================

// All these Apply* methods are simple wrappers to avoid returning an object.
// The default WASM bindings produce code that will leak if a return value
// isn't assigned to a JS variable and has delete() called on it.
// These Apply methods, combined with the smarter binding code allow for chainable
// commands that don't leak if the return value is ignored (i.e. when used intuitively).
void ApplyAddPath(SkPathBuilder& p,
                  const SkPath& newPath,
                  float scaleX,
                  float skewX,
                  float transX,
                  float skewY,
                  float scaleY,
                  float transY,
                  float pers0,
                  float pers1,
                  float pers2,
                  bool extendPath) {
    SkMatrix m =
            SkMatrix::MakeAll(scaleX, skewX, transX, skewY, scaleY, transY, pers0, pers1, pers2);
    p.addPath(newPath, m, extendPath ? SkPath::kExtend_AddPathMode : SkPath::kAppend_AddPathMode);
}

void ApplyArcToTangent(SkPathBuilder& p, float x1, float y1, float x2, float y2, float radius) {
    p.arcTo({x1, y1}, {x2, y2}, radius);
}

void ApplyArcToArcSize(SkPathBuilder& p,
                       float rx,
                       float ry,
                       float xAxisRotate,
                       bool useSmallArc,
                       bool ccw,
                       float x,
                       float y) {
    auto arcSize =
            useSmallArc ? SkPathBuilder::ArcSize::kSmall_ArcSize : SkPathBuilder::kLarge_ArcSize;
    auto sweep = ccw ? SkPathDirection::kCCW : SkPathDirection::kCW;
    p.arcTo({rx, ry}, xAxisRotate, arcSize, sweep, {x, y});
}

void ApplyRArcToArcSize(SkPathBuilder& p,
                        float rx,
                        float ry,
                        float xAxisRotate,
                        bool useSmallArc,
                        bool ccw,
                        float dx,
                        float dy) {
    auto arcSize = useSmallArc ? SkPathBuilder::ArcSize::kSmall_ArcSize
                               : SkPathBuilder::ArcSize::kLarge_ArcSize;
    auto sweep = ccw ? SkPathDirection::kCCW : SkPathDirection::kCW;
    p.rArcTo({rx, ry}, xAxisRotate, arcSize, sweep, {dx, dy});
}

void ApplyClose(SkPathBuilder& p) { p.close(); }

void ApplyConicTo(SkPathBuilder& p, float x1, float y1, float x2, float y2, float w) {
    p.conicTo({x1, y1}, {x2, y2}, w);
}

void ApplyRConicTo(SkPathBuilder& p, float dx1, float dy1, float dx2, float dy2, float w) {
    p.rConicTo({dx1, dy1}, {dx2, dy2}, w);
}

void ApplyCubicTo(SkPathBuilder& p, float x1, float y1, float x2, float y2, float x3, float y3) {
    p.cubicTo({x1, y1}, {x2, y2}, {x3, y3});
}

void ApplyRCubicTo(
        SkPathBuilder& p, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) {
    p.rCubicTo({dx1, dy1}, {dx2, dy2}, {dx3, dy3});
}

void ApplyLineTo(SkPathBuilder& p, float x, float y) { p.lineTo({x, y}); }

void ApplyRLineTo(SkPathBuilder& p, float dx, float dy) { p.rLineTo({dx, dy}); }

void ApplyMoveTo(SkPathBuilder& p, float x, float y) { p.moveTo({x, y}); }

void ApplyRMoveTo(SkPathBuilder& p, float dx, float dy) { p.rMoveTo({dx, dy}); }

void ApplyReset(SkPathBuilder& p) { p.reset(); }

void ApplyQuadTo(SkPathBuilder& p, float x1, float y1, float x2, float y2) {
    p.quadTo({x1, y1}, {x2, y2});
}

void ApplyRQuadTo(SkPathBuilder& p, float dx1, float dy1, float dx2, float dy2) {
    p.rQuadTo({dx1, dy1}, {dx2, dy2});
}

void ApplyTransform(SkPathBuilder& p,
                    float scaleX,
                    float skewX,
                    float transX,
                    float skewY,
                    float scaleY,
                    float transY,
                    float pers0,
                    float pers1,
                    float pers2) {
    SkMatrix m =
            SkMatrix::MakeAll(scaleX, skewX, transX, skewY, scaleY, transY, pers0, pers1, pers2);
    p.transform(m);
}

#ifdef CK_INCLUDE_PATHOPS
SkPathOrNull MakeSimplified(const SkPath& path) {
    if (auto result = Simplify(path)) {
        return emscripten::val(result.value());
    }
    return emscripten::val::null();
}

SkPathOrNull MakePathFromOp(const SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) {
    if (auto result = Op(pathOne, pathTwo, op)) {
        return emscripten::val(result.value());
    }
    return emscripten::val::null();
}

SkPathOrNull MakeAsWinding(const SkPath& self) {
    if (auto result = AsWinding(self)) {
        return emscripten::val(result.value());
    }
    return emscripten::val::null();
}
#endif

JSString ToSVGString(const SkPath& path) {
    return emscripten::val(SkParsePath::ToSVGString(path).c_str());
}

SkPathOrNull MakePathFromSVGString(std::string str) {
    if (auto path = SkParsePath::FromSVGString(str.c_str())) {
        return emscripten::val(*path);
    }
    return emscripten::val::null();
}

bool CanInterpolate(const SkPath& path1, const SkPath& path2) {
    return path1.isInterpolatable(path2);
}

SkPathOrNull MakePathFromInterpolation(const SkPath& path1, const SkPath& path2, float weight) {
    if (!path1.isInterpolatable(path2)) {
        return emscripten::val::null();
    }
    return emscripten::val(path1.makeInterpolate(path2, weight));
}

SkPath CopyPath(SkPath a) { return a; }

bool Equals(const SkPath& a, const SkPath& b) { return a == b; }

// =================================================================================
// Creating/Exporting Paths with cmd arrays
// =================================================================================

static const int MOVE = 0;
static const int LINE = 1;
static const int QUAD = 2;
static const int CONIC = 3;
static const int CUBIC = 4;
static const int CLOSE = 5;

Float32Array ToCmds(const SkPath& path) {
    std::vector<float> cmds;
    for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
        switch (verb) {
            case SkPathVerb::kMove:
                cmds.insert(cmds.end(), {MOVE, pts[0].x(), pts[0].y()});
                break;
            case SkPathVerb::kLine:
                cmds.insert(cmds.end(), {LINE, pts[1].x(), pts[1].y()});
                break;
            case SkPathVerb::kQuad:
                cmds.insert(cmds.end(), {QUAD, pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y()});
                break;
            case SkPathVerb::kConic:
                cmds.insert(cmds.end(),
                            {CONIC, pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y(), *w});
                break;
            case SkPathVerb::kCubic:
                cmds.insert(cmds.end(),
                            {CUBIC,
                             pts[1].x(),
                             pts[1].y(),
                             pts[2].x(),
                             pts[2].y(),
                             pts[3].x(),
                             pts[3].y()});
                break;
            case SkPathVerb::kClose:
                cmds.push_back(CLOSE);
                break;
        }
    }
    return MakeTypedArray(cmds.size(), (const float*)cmds.data());
}

SkPathOrNull MakePathFromCmds(WASMPointerF32 cptr, int numCmds) {
    const auto* cmds = reinterpret_cast<const float*>(cptr);
    SkPathBuilder path;
    float x1, y1, x2, y2, x3, y3;

// if there are not enough arguments, bail with the path we've constructed so far.
#define CHECK_NUM_ARGS(n)                                                           \
    if ((i + n) > numCmds) {                                                        \
        SkDebugf("Not enough args to match the verbs. Saw %d commands\n", numCmds); \
        return emscripten::val::null();                                             \
    }

    for (int i = 0; i < numCmds;) {
        switch (sk_float_floor2int(cmds[i++])) {
            case MOVE:
                CHECK_NUM_ARGS(2)
                x1 = cmds[i++];
                y1 = cmds[i++];
                path.moveTo(x1, y1);
                break;
            case LINE:
                CHECK_NUM_ARGS(2)
                x1 = cmds[i++];
                y1 = cmds[i++];
                path.lineTo(x1, y1);
                break;
            case QUAD:
                CHECK_NUM_ARGS(4)
                x1 = cmds[i++];
                y1 = cmds[i++];
                x2 = cmds[i++];
                y2 = cmds[i++];
                path.quadTo(x1, y1, x2, y2);
                break;
            case CONIC:
                CHECK_NUM_ARGS(5)
                x1 = cmds[i++];
                y1 = cmds[i++];
                x2 = cmds[i++];
                y2 = cmds[i++];
                x3 = cmds[i++];  // weight
                path.conicTo(x1, y1, x2, y2, x3);
                break;
            case CUBIC:
                CHECK_NUM_ARGS(6)
                x1 = cmds[i++];
                y1 = cmds[i++];
                x2 = cmds[i++];
                y2 = cmds[i++];
                x3 = cmds[i++];
                y3 = cmds[i++];
                path.cubicTo(x1, y1, x2, y2, x3, y3);
                break;
            case CLOSE:
                path.close();
                break;
            default:
                SkDebugf("  path: UNKNOWN command %f, aborting dump...\n", cmds[i - 1]);
                return emscripten::val::null();
        }
    }

#undef CHECK_NUM_ARGS

    return emscripten::val(path.detach());
}

SkPath MakePathFromVerbsPointsWeights(WASMPointerU8 verbsPtr,
                                      int numVerbs,
                                      WASMPointerF32 ptsPtr,
                                      int numPts,
                                      WASMPointerF32 wtsPtr,
                                      int numWts) {
    const SkPathVerb* verbs = reinterpret_cast<const SkPathVerb*>(verbsPtr);
    const SkPoint* pts = reinterpret_cast<const SkPoint*>(ptsPtr);
    const float* weights = reinterpret_cast<const float*>(wtsPtr);

    return SkPath::Raw(SkSpan<const SkPoint>(pts, SkToSizeT(numPts)),
                       SkSpan<const SkPathVerb>(verbs, SkToSizeT(numVerbs)),
                       SkSpan<const float>(weights, SkToSizeT(numWts)),
                       SkPathFillType::kDefault);
}

void PathAddVerbsPointsWeights(SkPathBuilder& self,
                               WASMPointerU8 verbsPtr,
                               int numVerbs,
                               WASMPointerF32 ptsPtr,
                               int numPts,
                               WASMPointerF32 wtsPtr,
                               int numWts) {
    const SkPathVerb* verbs = reinterpret_cast<const SkPathVerb*>(verbsPtr);
    const SkPoint* pts = reinterpret_cast<const SkPoint*>(ptsPtr);
    const float* weights = reinterpret_cast<const float*>(wtsPtr);

    SkPathRaw raw = SkPathRaw{
        SkSpan<const SkPoint>(pts, SkToSizeT(numPts)),
        SkSpan<const SkPathVerb>(verbs, SkToSizeT(numVerbs)),
        SkSpan<const float>(weights, SkToSizeT(numWts)),
    };
    self.addRaw(raw);
}

//========================================================================================
// Path Effects
//========================================================================================

SkPathOrNull MakeDashed(const SkPath& self, float on, float off, float phase) {
    float intervals[] = {on, off};
    auto pe = SkDashPathEffect::Make(intervals, phase);
    if (!pe) {
        SkDebugf("Invalid args to dash()\n");
        return emscripten::val::null();
    }
    SkStrokeRec rec(SkStrokeRec::InitStyle::kHairline_InitStyle);
    SkPathBuilder pb;
    if (pe->filterPath(&pb, self, &rec)) {
        return emscripten::val(pb.detach());
    }
    SkDebugf("Could not make dashed path\n");
    return emscripten::val::null();
}

SkPathOrNull MakeTrimmed(const SkPath& self, float startT, float stopT, bool isComplement) {
    auto mode = isComplement ? SkTrimPathEffect::Mode::kInverted : SkTrimPathEffect::Mode::kNormal;
    auto pe = SkTrimPathEffect::Make(startT, stopT, mode);
    if (!pe) {
        SkDebugf("Invalid args to trim(): startT and stopT must be in [0,1]\n");
        return emscripten::val::null();
    }
    SkStrokeRec rec(SkStrokeRec::InitStyle::kHairline_InitStyle);
    SkPathBuilder pb;
    if (pe->filterPath(&pb, self, &rec)) {
        return emscripten::val(pb.detach());
    }
    SkDebugf("Could not trim path\n");
    return emscripten::val::null();
}

struct StrokeOpts {
    // Default values are set in interface.js which allows clients
    // to set any number of them. Otherwise, the binding code complains if
    // any are omitted.
    float width;
    float miter_limit;
    SkPaint::Join join;
    SkPaint::Cap cap;
    float precision;
};

SkPathOrNull MakeStroked(const SkPath& self, StrokeOpts opts) {
    SkPaint p;
    p.setStyle(SkPaint::kStroke_Style);
    p.setStrokeCap(opts.cap);
    p.setStrokeJoin(opts.join);
    p.setStrokeWidth(opts.width);
    p.setStrokeMiter(opts.miter_limit);

    SkMatrix scale = SkMatrix::Scale(opts.precision, opts.precision);

    SkPathBuilder pb;
    if (skpathutils::FillPathWithPaint(self, p, &pb, nullptr, scale)) {
        return emscripten::val(pb.detach());
    }
    return emscripten::val::null();
}

// This function is private, we call it in interface.js
void computeTonalColors(WASMPointerF32 cPtrAmbi, WASMPointerF32 cPtrSpot) {
    // private methods accepting colors take pointers to floats already copied into wasm memory.
    float* ambiFloats = reinterpret_cast<float*>(cPtrAmbi);
    float* spotFloats = reinterpret_cast<float*>(cPtrSpot);
    SkColor4f ambiColor = {ambiFloats[0], ambiFloats[1], ambiFloats[2], ambiFloats[3]};
    SkColor4f spotColor = {spotFloats[0], spotFloats[1], spotFloats[2], spotFloats[3]};

    // This function takes SkColor
    SkColor resultAmbi, resultSpot;
    SkShadowUtils::ComputeTonalColors(
            ambiColor.toSkColor(), spotColor.toSkColor(), &resultAmbi, &resultSpot);

    // Convert back to color4f
    const SkColor4f ambi4f = SkColor4f::FromColor(resultAmbi);
    const SkColor4f spot4f = SkColor4f::FromColor(resultSpot);

    // Re-use the caller's allocated memory to hold the result.
    memcpy(ambiFloats, ambi4f.vec(), 4 * sizeof(float));
    memcpy(spotFloats, spot4f.vec(), 4 * sizeof(float));
}

#ifdef CK_INCLUDE_RUNTIME_EFFECT
struct RuntimeEffectUniform {
    int columns;
    int rows;
    int slot;  // the index into the uniforms array that this uniform begins.
    bool isInteger;
};

RuntimeEffectUniform fromUniform(const SkRuntimeEffect::Uniform& u) {
    RuntimeEffectUniform su;
    su.rows = u.count;  // arrayLength
    su.columns = 1;
    su.isInteger = false;
    using Type = SkRuntimeEffect::Uniform::Type;
    switch (u.type) {
        case Type::kFloat:
            break;
        case Type::kFloat2:
            su.columns = 2;
            break;
        case Type::kFloat3:
            su.columns = 3;
            break;
        case Type::kFloat4:
            su.columns = 4;
            break;
        case Type::kFloat2x2:
            su.columns = 2;
            su.rows *= 2;
            break;
        case Type::kFloat3x3:
            su.columns = 3;
            su.rows *= 3;
            break;
        case Type::kFloat4x4:
            su.columns = 4;
            su.rows *= 4;
            break;
        case Type::kInt:
            su.isInteger = true;
            break;
        case Type::kInt2:
            su.columns = 2;
            su.isInteger = true;
            break;
        case Type::kInt3:
            su.columns = 3;
            su.isInteger = true;
            break;
        case Type::kInt4:
            su.columns = 4;
            su.isInteger = true;
            break;
    }
    su.slot = u.offset / sizeof(float);
    return su;
}

void castUniforms(void* data, size_t dataLen, const SkRuntimeEffect& effect) {
    if (dataLen != effect.uniformSize()) {
        // Incorrect number of uniforms. Our code below could read/write off the end of the buffer.
        // However, shader creation is going to fail anyway, so just do nothing.
        return;
    }

    float* fltData = reinterpret_cast<float*>(data);
    for (const auto& u : effect.uniforms()) {
        RuntimeEffectUniform reu = fromUniform(u);
        if (reu.isInteger) {
            // The SkSL is expecting integers in the uniform data
            for (int i = 0; i < reu.columns * reu.rows; ++i) {
                int numAsInt = static_cast<int>(fltData[reu.slot + i]);
                fltData[reu.slot + i] = SkBits2Float(numAsInt);
            }
        }
    }
}
#endif

sk_sp<SkData> alwaysSaveTypefaceBytes(SkTypeface* face, void*) {
    return face->serialize(SkTypeface::SerializeBehavior::kDoIncludeData);
}

// These objects have private destructors / delete methods - I don't think
// we need to do anything other than tell emscripten to do nothing.
namespace emscripten {
namespace internal {
template <typename ClassType> void raw_destructor(ClassType*);

template <> void raw_destructor<SkContourMeasure>(SkContourMeasure* ptr) {}

template <> void raw_destructor<SkVertices>(SkVertices* ptr) {}

#ifndef CK_NO_FONTS
template <> void raw_destructor<SkTextBlob>(SkTextBlob* ptr) {}

template <> void raw_destructor<SkTypeface>(SkTypeface* ptr) {}
#endif
}  // namespace internal
}  // namespace emscripten

// toBytes returns a Uint8Array that has a copy of the data in the given SkData.
Uint8Array toBytes(sk_sp<SkData> data) {
    // By making the copy using the JS transliteration, we don't risk the SkData object being
    // cleaned up before we make the copy.
    return emscripten::val(
                   // https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html#memory-views
                   typed_memory_view(data->size(), data->bytes()))
            .call<Uint8Array>("slice");  // slice with no args makes a copy of the memory view.
}

#ifdef CK_ENABLE_WEBGL
// We need to call into the JS side of things to free webGL contexts. This object will be called
// with _setTextureCleanup after CanvasKit loads. The object will have one attribute,
// a function called deleteTexture that takes two ints.
JSObject textureCleanup = emscripten::val::null();

struct TextureReleaseContext {
    // This refers to which webgl context, i.e. which surface, owns the texture. We need this
    // to route the deleteTexture to the right context.
    uint32_t webglHandle;
    // This refers to the index of the texture in the complete list of textures.
    uint32_t texHandle;
};

void deleteJSTexture(SkImages::ReleaseContext rc) {
    auto ctx = reinterpret_cast<TextureReleaseContext*>(rc);
    textureCleanup.call<void>("deleteTexture", ctx->webglHandle, ctx->texHandle);
    delete ctx;
}

class ExternalWebGLTexture : public GrExternalTexture {
public:
    ExternalWebGLTexture(GrBackendTexture backendTexture,
                         uint32_t textureHandle,
                         EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context)
            : fBackendTexture(backendTexture)
            , fWebglHandle(context)
            , fTextureHandle(textureHandle) {}

    GrBackendTexture getBackendTexture() override { return fBackendTexture; }

    void dispose() override {
        textureCleanup.call<void>("deleteTexture", fWebglHandle, fTextureHandle);
    }

private:
    GrBackendTexture fBackendTexture;

    // This refers to which webgl context, i.e. which surface, owns the texture. We need this
    // to route the deleteTexture to the right context.
    uint32_t fWebglHandle;
    // This refers to the index of the texture in the complete list of textures.
    uint32_t fTextureHandle;
};

class WebGLTextureImageGenerator : public GrExternalTextureGenerator {
public:
    WebGLTextureImageGenerator(SkImageInfo ii, JSObject callbackObj)
            : GrExternalTextureGenerator(ii), fCallback(callbackObj) {}

    ~WebGLTextureImageGenerator() override {
        // This cleans up the associated TextureSource that is used to make the texture
        // (i.e. "makeTexture" below). We expect this destructor to be called when the
        // SkImage that this Generator belongs to is destroyed.
        fCallback.call<void>("freeSrc");
    }

    std::unique_ptr<GrExternalTexture> generateExternalTexture(GrRecordingContext* ctx,
                                                               skgpu::Mipmapped) override {
        GrGLTextureInfo glInfo;

        // This callback is defined in webgl.js
        glInfo.fID = fCallback.call<uint32_t>("makeTexture");

        // The format and target should match how we make the texture on the JS side
        // See the implementation of the makeTexture function.
        glInfo.fFormat = GR_GL_RGBA8;
        glInfo.fTarget = GR_GL_TEXTURE_2D;

        // These textures are unlikely to actually have mipmaps generated (we might even be on
        // WebGL 1, where Skia doesn't support mipmapping at all). Therefore, we ignore any request
        // for mipmapping here. See: b/338095525
        auto backendTexture = GrBackendTextures::MakeGL(
                fInfo.width(), fInfo.height(), skgpu::Mipmapped::kNo, glInfo);

        // In order to bind the image source to the texture, makeTexture has changed which
        // texture is "in focus" for the WebGL context.
        GrAsDirectContext(ctx)->resetContext(kTextureBinding_GrGLBackendState);
        return std::make_unique<ExternalWebGLTexture>(
                backendTexture, glInfo.fID, emscripten_webgl_get_current_context());
    }

private:
    JSObject fCallback;
};

// callbackObj has two functions in it, one to create a texture "makeTexture" and one to clean up
// the underlying texture source "freeSrc". This way, we can create WebGL textures for each
// surface/WebGLContext that the image is used on (we cannot share WebGLTextures across contexts).
sk_sp<SkImage> MakeImageFromGenerator(SimpleImageInfo ii, JSObject callbackObj) {
    auto gen = std::make_unique<WebGLTextureImageGenerator>(toSkImageInfo(ii), callbackObj);
    return SkImages::DeferredFromTextureGenerator(std::move(gen));
}
#endif  // CK_ENABLE_WEBGL

static Uint8Array encodeImage(GrDirectContext* dContext,
                              sk_sp<SkImage> img,
                              SkEncodedImageFormat fmt,
                              int quality) {
    sk_sp<SkData> data = nullptr;
    if (fmt == SkEncodedImageFormat::kJPEG) {
        SkJpegEncoder::Options opts;
        opts.fQuality = quality;
        data = SkJpegEncoder::Encode(dContext, img.get(), opts);
    } else if (fmt == SkEncodedImageFormat::kPNG) {
        data = SkPngEncoder::Encode(dContext, img.get(), {});
    } else {
        SkWebpEncoder::Options opts;
        if (quality >= 100) {
            opts.fCompression = SkWebpEncoder::Compression::kLossless;
            opts.fQuality = 75;  // This is effort to compress
        } else {
            opts.fCompression = SkWebpEncoder::Compression::kLossy;
            opts.fQuality = quality;
        }
        data = SkWebpEncoder::Encode(dContext, img.get(), opts);
    }
    if (!data) {
        return emscripten::val::null();
    }
    return toBytes(data);
}

EMSCRIPTEN_BINDINGS(Skia) {
#ifdef ENABLE_GPU
    constant("gpu", true);
    function("_MakeGrContext", &MakeGrContext);
#endif  // ENABLE_GPU

#ifdef CK_ENABLE_WEBGL
    constant("webgl", true);
    function("_MakeOnScreenGLSurface",
             select_overload<sk_sp<SkSurface>(
                     sk_sp<GrDirectContext>, int, int, sk_sp<SkColorSpace>)>(
                     &MakeOnScreenGLSurface));
    function("_MakeOnScreenGLSurface",
             select_overload<sk_sp<SkSurface>(
                     sk_sp<GrDirectContext>, int, int, sk_sp<SkColorSpace>, int, int)>(
                     &MakeOnScreenGLSurface));
    function(
            "_MakeRenderTargetWH",
            select_overload<sk_sp<SkSurface>(sk_sp<GrDirectContext>, int, int)>(&MakeRenderTarget));
    function("_MakeRenderTargetII",
             select_overload<sk_sp<SkSurface>(sk_sp<GrDirectContext>, SimpleImageInfo)>(
                     &MakeRenderTarget));
#endif  // CK_ENABLE_WEBGL

#ifdef CK_ENABLE_WEBGPU
    constant("webgpu", true);
    function("_MakeGPUTextureSurface", &MakeGPUTextureSurface);
#endif  // CK_ENABLE_WEBGPU

    function("getDecodeCacheLimitBytes", &SkResourceCache::GetTotalByteLimit);
    function("setDecodeCacheLimitBytes", &SkResourceCache::SetTotalByteLimit);
    function("getDecodeCacheUsedBytes", &SkResourceCache::GetTotalBytesUsed);

    function("_computeTonalColors", &computeTonalColors);
    function("_decodeAnimatedImage",
             optional_override([](WASMPointerU8 iptr, size_t length) -> sk_sp<SkAnimatedImage> {
                 uint8_t* imgData = reinterpret_cast<uint8_t*>(iptr);
                 auto bytes = SkData::MakeFromMalloc(imgData, length);
                 auto codec = DecodeImageData(bytes);
                 if (codec == nullptr) {
                     return nullptr;
                 }
                 auto aCodec = SkAndroidCodec::MakeFromCodec(std::move(codec));
                 if (aCodec == nullptr) {
                     return nullptr;
                 }

                 return SkAnimatedImage::Make(std::move(aCodec));
             }),
             allow_raw_pointers());
    function("_decodeImage",
             optional_override([](WASMPointerU8 iptr, size_t length) -> sk_sp<SkImage> {
                 uint8_t* imgData = reinterpret_cast<uint8_t*>(iptr);
                 auto bytes = SkData::MakeFromMalloc(imgData, length);
                 auto codec = DecodeImageData(bytes);
                 if (codec == nullptr) {
                     return nullptr;
                 }
                 return std::get<0>(codec->getImage());
             }),
             allow_raw_pointers());

    // These won't be called directly, there are corresponding JS helpers to deal with arrays.
    function("_MakeImage",
             optional_override([](SimpleImageInfo ii, WASMPointerU8 pPtr, int plen, size_t rowBytes)
                                       -> sk_sp<SkImage> {
                 uint8_t* pixels = reinterpret_cast<uint8_t*>(pPtr);
                 SkImageInfo info = toSkImageInfo(ii);
                 sk_sp<SkData> pixelData = SkData::MakeFromMalloc(pixels, plen);

                 return SkImages::RasterFromData(info, pixelData, rowBytes);
             }),
             allow_raw_pointers());

    function("_getShadowLocalBounds",
             optional_override([](WASMPointerF32 ctmPtr,
                                  const SkPath& path,
                                  WASMPointerF32 zPlaneParamPtr,
                                  WASMPointerF32 lightPosPtr,
                                  float lightRadius,
                                  uint32_t flags,
                                  WASMPointerF32 outPtr) -> bool {
                 SkMatrix ctm;
                 const float* nineMatrixValues = reinterpret_cast<const float*>(ctmPtr);
                 ctm.set9(nineMatrixValues);
                 const SkVector3* zPlaneParams = reinterpret_cast<const SkVector3*>(zPlaneParamPtr);
                 const SkVector3* lightPos = reinterpret_cast<const SkVector3*>(lightPosPtr);
                 SkRect* outputBounds = reinterpret_cast<SkRect*>(outPtr);
                 return SkShadowUtils::GetLocalBounds(
                         ctm, path, *zPlaneParams, *lightPos, lightRadius, flags, outputBounds);
             }));

#ifdef CK_SERIALIZE_SKP
    function("_MakePicture",
             optional_override([](WASMPointerU8 dPtr, size_t bytes) -> sk_sp<SkPicture> {
                 uint8_t* d = reinterpret_cast<uint8_t*>(dPtr);
                 sk_sp<SkData> data = SkData::MakeFromMalloc(d, bytes);

#ifndef CK_NO_FONTS
                 // Be sure we can process the data stored when serializing the SkPicture.
                 static SkOnce once;
                 once([] {
                     SkTypeface::Register(SkTypeface_FreeType::FactoryId,
                                          SkTypeface_FreeType::MakeFromStream);
                 });
#endif

                 SkDeserialProcs dp;
                 dp.fImageDataProc = [](sk_sp<SkData> bytes,
                                        std::optional<SkAlphaType> at,
                                        void* ctx) -> sk_sp<SkImage> {
                     auto codec = DecodeImageData(bytes);
                     if (codec == nullptr) {
                         return nullptr;
                     }
                     SkImageInfo info = codec->getInfo();
                     if (at.has_value()) {
                         info = info.makeAlphaType(*at);
                     } else if (kUnpremul_SkAlphaType == info.alphaType()) {
                         // Otherwise, prefer premul over unpremul (this produces better filtering
                         // in general)
                         info = info.makeAlphaType(kPremul_SkAlphaType);
                     }
                     return std::get<0>(codec->getImage(info));
                 };

                 return SkPicture::MakeFromData(data.get(), nullptr);
             }),
             allow_raw_pointers());
#endif

#ifdef ENABLE_GPU
    class_<GrDirectContext>("GrDirectContext")
            .smart_ptr<sk_sp<GrDirectContext>>("sk_sp<GrDirectContext>")
            .function("_getResourceCacheLimitBytes",
                      optional_override([](GrDirectContext& self) -> size_t {
                          int maxResources = 0;  // ignored
                          size_t currMax = 0;
                          self.getResourceCacheLimits(&maxResources, &currMax);
                          return currMax;
                      }))
            .function("_getResourceCacheUsageBytes",
                      optional_override([](GrDirectContext& self) -> size_t {
                          int usedResources = 0;  // ignored
                          size_t currUsage = 0;
                          self.getResourceCacheUsage(&usedResources, &currUsage);
                          return currUsage;
                      }))
            .function("_releaseResourcesAndAbandonContext",
                      &GrDirectContext::releaseResourcesAndAbandonContext)
            .function("_setResourceCacheLimitBytes",
                      optional_override([](GrDirectContext& self, size_t maxResourceBytes) -> void {
                          int maxResources = 0;
                          size_t currMax = 0;  // ignored
                          self.getResourceCacheLimits(&maxResources, &currMax);
                          self.setResourceCacheLimits(maxResources, maxResourceBytes);
                      }));
#endif  // ENABLE_GPU
#ifdef CK_ENABLE_WEBGL
    // This allows us to give the C++ code a JS callback to delete textures that
    // have been passed in via makeImageFromTexture and makeImageFromTextureSource.
    function("_setTextureCleanup",
             optional_override([](JSObject callbackObj) -> void { textureCleanup = callbackObj; }));
#endif

    class_<SkAnimatedImage>("AnimatedImage")
            .smart_ptr<sk_sp<SkAnimatedImage>>("sk_sp<AnimatedImage>")
            .function("currentFrameDuration", &SkAnimatedImage::currentFrameDuration)
            .function("decodeNextFrame", &SkAnimatedImage::decodeNextFrame)
            .function("getFrameCount", &SkAnimatedImage::getFrameCount)
            .function("getRepetitionCount", &SkAnimatedImage::getRepetitionCount)
            .function("height", optional_override([](SkAnimatedImage& self) -> int32_t {
                          // getBounds returns an SkRect, but internally, the width and height are
                          // ints.
                          return SkScalarFloorToInt(self.getBounds().height());
                      }))
            .function("makeImageAtCurrentFrame", &SkAnimatedImage::getCurrentFrame)
            .function("reset", &SkAnimatedImage::reset)
            .function("width", optional_override([](SkAnimatedImage& self) -> int32_t {
                          return SkScalarFloorToInt(self.getBounds().width());
                      }));

    class_<SkBlender>("Blender")
            .smart_ptr<sk_sp<SkBlender>>("sk_sp<Blender>")
            .class_function("Mode", &SkBlender::Mode);

    class_<SkCanvas>("Canvas")
            .constructor<>()
            .constructor<float, float>()
            .function("_clear", optional_override([](SkCanvas& self, WASMPointerF32 cPtr) {
                          self.clear(ptrToSkColor4f(cPtr));
                      }))
            .function("clipPath",
                      select_overload<void(const SkPath&, SkClipOp, bool)>(&SkCanvas::clipPath))
            .function(
                    "_clipRRect",
                    optional_override(
                            [](SkCanvas& self, WASMPointerF32 fPtr, SkClipOp op, bool doAntiAlias) {
                                self.clipRRect(ptrToSkRRect(fPtr), op, doAntiAlias);
                            }))
            .function(
                    "_clipRect",
                    optional_override(
                            [](SkCanvas& self, WASMPointerF32 fPtr, SkClipOp op, bool doAntiAlias) {
                                const SkRect* rect = reinterpret_cast<const SkRect*>(fPtr);
                                self.clipRect(*rect, op, doAntiAlias);
                            }))
            .function("_concat", optional_override([](SkCanvas& self, WASMPointerF32 mPtr) {
                          // TODO(skbug.com/40041444): make the JS side be column major.
                          const float* sixteenMatrixValues = reinterpret_cast<const float*>(mPtr);
                          SkM44 m = SkM44::RowMajor(sixteenMatrixValues);
                          self.concat(m);
                      }))
            .function("_drawArc",
                      optional_override([](SkCanvas& self,
                                           WASMPointerF32 fPtr,
                                           float startAngle,
                                           float sweepAngle,
                                           bool useCenter,
                                           const SkPaint& paint) {
                          const SkRect* oval = reinterpret_cast<const SkRect*>(fPtr);
                          self.drawArc(*oval, startAngle, sweepAngle, useCenter, paint);
                      }))
            .function("_drawAtlasOptions",
                      optional_override([](SkCanvas& self,
                                           const sk_sp<SkImage>& atlas,
                                           WASMPointerF32 xptr,
                                           WASMPointerF32 rptr,
                                           WASMPointerU32 cptr,
                                           int count,
                                           SkBlendMode mode,
                                           SkFilterMode filter,
                                           SkMipmapMode mipmap,
                                           const SkPaint* paint) -> void {
                          const SkRSXform* dstXforms = reinterpret_cast<const SkRSXform*>(xptr);
                          const SkRect* srcRects = reinterpret_cast<const SkRect*>(rptr);
                          const SkColor* colors = nullptr;
                          if (cptr) {
                              colors = reinterpret_cast<const SkColor*>(cptr);
                          }
                          SkSamplingOptions sampling(filter, mipmap);
                          self.drawAtlas(atlas.get(),
                                         {dstXforms, count},
                                         {srcRects, count},
                                         {colors, colors ? count : 0},
                                         mode,
                                         sampling,
                                         nullptr,
                                         paint);
                      }),
                      allow_raw_pointers())
            .function("_drawAtlasCubic",
                      optional_override([](SkCanvas& self,
                                           const sk_sp<SkImage>& atlas,
                                           WASMPointerF32 xptr,
                                           WASMPointerF32 rptr,
                                           WASMPointerU32 cptr,
                                           int count,
                                           SkBlendMode mode,
                                           float B,
                                           float C,
                                           const SkPaint* paint) -> void {
                          const SkRSXform* dstXforms = reinterpret_cast<const SkRSXform*>(xptr);
                          const SkRect* srcRects = reinterpret_cast<const SkRect*>(rptr);
                          const SkColor* colors = nullptr;
                          if (cptr) {
                              colors = reinterpret_cast<const SkColor*>(cptr);
                          }
                          SkSamplingOptions sampling({B, C});
                          self.drawAtlas(atlas.get(),
                                         {dstXforms, count},
                                         {srcRects, count},
                                         {colors, colors ? count : 0},
                                         mode,
                                         sampling,
                                         nullptr,
                                         paint);
                      }),
                      allow_raw_pointers())
            .function("_drawCircle",
                      select_overload<void(float, float, float, const SkPaint& paint)>(
                              &SkCanvas::drawCircle))
            .function("_drawColor", optional_override([](SkCanvas& self, WASMPointerF32 cPtr) {
                          self.drawColor(ptrToSkColor4f(cPtr));
                      }))
            .function("_drawColor",
                      optional_override([](SkCanvas& self, WASMPointerF32 cPtr, SkBlendMode mode) {
                          self.drawColor(ptrToSkColor4f(cPtr), mode);
                      }))
            .function("_drawColorInt",
                      optional_override([](SkCanvas& self, SkColor color, SkBlendMode mode) {
                          self.drawColor(color, mode);
                      }))
            .function("_drawDRRect",
                      optional_override([](SkCanvas& self,
                                           WASMPointerF32 outerPtr,
                                           WASMPointerF32 innerPtr,
                                           const SkPaint& paint) {
                          self.drawDRRect(ptrToSkRRect(outerPtr), ptrToSkRRect(innerPtr), paint);
                      }))
            .function("_drawGlyphs",
                      optional_override([](SkCanvas& self,
                                           int count,
                                           WASMPointerU16 glyphs,
                                           WASMPointerF32 positions,
                                           float x,
                                           float y,
                                           const SkFont& font,
                                           const SkPaint& paint) -> void {
                          self.drawGlyphs({reinterpret_cast<const uint16_t*>(glyphs), count},
                                          {reinterpret_cast<const SkPoint*>(positions), count},
                                          {x, y},
                                          font,
                                          paint);
                      }))
            // TODO: deprecate this version, and require sampling
            .function("_drawImage",
                      optional_override([](SkCanvas& self,
                                           const sk_sp<SkImage>& image,
                                           float x,
                                           float y,
                                           const SkPaint* paint) {
                          self.drawImage(image.get(), x, y, SkSamplingOptions(), paint);
                      }),
                      allow_raw_pointers())
            .function("_drawImageCubic",
                      optional_override([](SkCanvas& self,
                                           const sk_sp<SkImage>& img,
                                           float left,
                                           float top,
                                           float B,
                                           float C,  // See SkSamplingOptions.h for docs.
                                           const SkPaint* paint) -> void {
                          self.drawImage(img.get(), left, top, SkSamplingOptions({B, C}), paint);
                      }),
                      allow_raw_pointers())
            .function("_drawImageOptions",
                      optional_override([](SkCanvas& self,
                                           const sk_sp<SkImage>& img,
                                           float left,
                                           float top,
                                           SkFilterMode filter,
                                           SkMipmapMode mipmap,
                                           const SkPaint* paint) -> void {
                          self.drawImage(img.get(), left, top, {filter, mipmap}, paint);
                      }),
                      allow_raw_pointers())

            .function("_drawImageNine",
                      optional_override([](SkCanvas& self,
                                           const sk_sp<SkImage>& image,
                                           WASMPointerU32 centerPtr,
                                           WASMPointerF32 dstPtr,
                                           SkFilterMode filter,
                                           const SkPaint* paint) -> void {
                          const SkIRect* center = reinterpret_cast<const SkIRect*>(centerPtr);
                          const SkRect* dst = reinterpret_cast<const SkRect*>(dstPtr);

                          self.drawImageNine(image.get(), *center, *dst, filter, paint);
                      }),
                      allow_raw_pointers())
            // TODO: deprecate this version, and require sampling
            .function("_drawImageRect",
                      optional_override([](SkCanvas& self,
                                           const sk_sp<SkImage>& image,
                                           WASMPointerF32 srcPtr,
                                           WASMPointerF32 dstPtr,
                                           const SkPaint* paint,
                                           bool fastSample) -> void {
                          const SkRect* src = reinterpret_cast<const SkRect*>(srcPtr);
                          const SkRect* dst = reinterpret_cast<const SkRect*>(dstPtr);
                          self.drawImageRect(image,
                                             *src,
                                             *dst,
                                             SkSamplingOptions(),
                                             paint,
                                             fastSample ? SkCanvas::kFast_SrcRectConstraint
                                                        : SkCanvas::kStrict_SrcRectConstraint);
                      }),
                      allow_raw_pointers())
            .function("_drawImageRectCubic",
                      optional_override([](SkCanvas& self,
                                           const sk_sp<SkImage>& image,
                                           WASMPointerF32 srcPtr,
                                           WASMPointerF32 dstPtr,
                                           float B,
                                           float C,  // See SkSamplingOptions.h for docs.
                                           const SkPaint* paint) -> void {
                          const SkRect* src = reinterpret_cast<const SkRect*>(srcPtr);
                          const SkRect* dst = reinterpret_cast<const SkRect*>(dstPtr);
                          auto constraint =
                                  SkCanvas::kStrict_SrcRectConstraint;  // TODO: get from caller
                          self.drawImageRect(image.get(),
                                             *src,
                                             *dst,
                                             SkSamplingOptions({B, C}),
                                             paint,
                                             constraint);
                      }),
                      allow_raw_pointers())
            .function("_drawImageRectOptions",
                      optional_override([](SkCanvas& self,
                                           const sk_sp<SkImage>& image,
                                           WASMPointerF32 srcPtr,
                                           WASMPointerF32 dstPtr,
                                           SkFilterMode filter,
                                           SkMipmapMode mipmap,
                                           const SkPaint* paint) -> void {
                          const SkRect* src = reinterpret_cast<const SkRect*>(srcPtr);
                          const SkRect* dst = reinterpret_cast<const SkRect*>(dstPtr);
                          auto constraint =
                                  SkCanvas::kStrict_SrcRectConstraint;  // TODO: get from caller
                          self.drawImageRect(
                                  image.get(), *src, *dst, {filter, mipmap}, paint, constraint);
                      }),
                      allow_raw_pointers())
            .function("_drawLine",
                      select_overload<void(float, float, float, float, const SkPaint&)>(
                              &SkCanvas::drawLine))
            .function(
                    "_drawOval",
                    optional_override(
                            [](SkCanvas& self, WASMPointerF32 fPtr, const SkPaint& paint) -> void {
                                const SkRect* oval = reinterpret_cast<const SkRect*>(fPtr);
                                self.drawOval(*oval, paint);
                            }))
            .function("_drawPaint", &SkCanvas::drawPaint)
#ifdef CK_INCLUDE_PARAGRAPH
            .function("_drawParagraph",
                      optional_override(
                              [](SkCanvas& self, skia::textlayout::Paragraph* p, float x, float y) {
                                  p->paint(&self, x, y);
                              }),
                      allow_raw_pointers())
#endif
            .function("_drawPath", &SkCanvas::drawPath)
            .function("_drawPatch",
                      optional_override([](SkCanvas& self,
                                           WASMPointerF32 cubics,
                                           WASMPointerU32 colors,
                                           WASMPointerF32 texs,
                                           SkBlendMode mode,
                                           const SkPaint& paint) -> void {
                          self.drawPatch(reinterpret_cast<const SkPoint*>(cubics),
                                         reinterpret_cast<const SkColor*>(colors),
                                         reinterpret_cast<const SkPoint*>(texs),
                                         mode,
                                         paint);
                      }))
            // Of note, picture is *not* what is colloquially thought of as a "picture", what we
            // call a bitmap. An SkPicture is a series of draw commands.
            .function("_drawPicture",
                      select_overload<void(const sk_sp<SkPicture>&)>(&SkCanvas::drawPicture))
            .function("_drawPoints",
                      optional_override([](SkCanvas& self,
                                           SkCanvas::PointMode mode,
                                           WASMPointerF32 pptr,
                                           int count,
                                           SkPaint& paint) -> void {
                          const SkPoint* pts = reinterpret_cast<const SkPoint*>(pptr);
                          self.drawPoints(mode, {pts, count}, paint);
                      }))
            .function("_drawRRect",
                      optional_override(
                              [](SkCanvas& self, WASMPointerF32 fPtr, const SkPaint& paint) {
                                  self.drawRRect(ptrToSkRRect(fPtr), paint);
                              }))
            .function(
                    "_drawRect",
                    optional_override(
                            [](SkCanvas& self, WASMPointerF32 fPtr, const SkPaint& paint) -> void {
                                const SkRect* rect = reinterpret_cast<const SkRect*>(fPtr);
                                self.drawRect(*rect, paint);
                            }))
            .function("_drawRect4f",
                      optional_override([](SkCanvas& self,
                                           float left,
                                           float top,
                                           float right,
                                           float bottom,
                                           const SkPaint& paint) -> void {
                          const SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
                          self.drawRect(rect, paint);
                      }))
            .function("_drawShadow",
                      optional_override([](SkCanvas& self,
                                           const SkPath& path,
                                           WASMPointerF32 zPlaneParamPtr,
                                           WASMPointerF32 lightPosPtr,
                                           float lightRadius,
                                           WASMPointerF32 ambientColorPtr,
                                           WASMPointerF32 spotColorPtr,
                                           uint32_t flags) {
                          const SkVector3* zPlaneParams =
                                  reinterpret_cast<const SkVector3*>(zPlaneParamPtr);
                          const SkVector3* lightPos =
                                  reinterpret_cast<const SkVector3*>(lightPosPtr);

                          SkShadowUtils::DrawShadow(&self,
                                                    path,
                                                    *zPlaneParams,
                                                    *lightPos,
                                                    lightRadius,
                                                    ptrToSkColor4f(ambientColorPtr).toSkColor(),
                                                    ptrToSkColor4f(spotColorPtr).toSkColor(),
                                                    flags);
                      }))
#ifndef CK_NO_FONTS
            .function("_drawSimpleText",
                      optional_override([](SkCanvas& self,
                                           WASMPointerU8 sptr,
                                           size_t len,
                                           float x,
                                           float y,
                                           const SkFont& font,
                                           const SkPaint& paint) {
                          const char* str = reinterpret_cast<const char*>(sptr);

                          self.drawSimpleText(str, len, SkTextEncoding::kUTF8, x, y, font, paint);
                      }))
            .function("_drawTextBlob",
                      select_overload<void(const sk_sp<SkTextBlob>&, float, float, const SkPaint&)>(
                              &SkCanvas::drawTextBlob))
#endif
            .function("_drawVertices",
                      select_overload<void(const sk_sp<SkVertices>&, SkBlendMode, const SkPaint&)>(
                              &SkCanvas::drawVertices))

            .function("_getDeviceClipBounds",
                      optional_override([](const SkCanvas& self, WASMPointerI32 iPtr) {
                          SkIRect* outputRect = reinterpret_cast<SkIRect*>(iPtr);
                          if (!outputRect) {
                              return;  // output pointer cannot be null
                          }
                          self.getDeviceClipBounds(outputRect);
                      }))

            .function("_quickReject",
                      optional_override([](const SkCanvas& self, WASMPointerF32 fPtr) -> bool {
                          const SkRect* rect = reinterpret_cast<const SkRect*>(fPtr);
                          return self.quickReject(*rect);
                      }))

            // 4x4 matrix functions
            // Just like with getTotalMatrix, we allocate the buffer for the 16 floats to go in from
            // interface.js, so it can also free them when its done.
            .function("_getLocalToDevice",
                      optional_override([](const SkCanvas& self, WASMPointerF32 mPtr) {
                          float* sixteenMatrixValues = reinterpret_cast<float*>(mPtr);
                          if (!sixteenMatrixValues) {
                              return;  // matrix cannot be null
                          }
                          SkM44 m = self.getLocalToDevice();
                          m.getRowMajor(sixteenMatrixValues);
                      }))
            .function("getSaveCount", &SkCanvas::getSaveCount)
            // We allocate room for the matrix from the JS side and free it there so as to not have
            // an awkward moment where we malloc something here and "just know" to free it on the
            // JS side.
            .function("_getTotalMatrix",
                      optional_override([](const SkCanvas& self, WASMPointerU8 mPtr) {
                          float* nineMatrixValues = reinterpret_cast<float*>(mPtr);
                          if (!nineMatrixValues) {
                              return;  // matrix cannot be null
                          }
                          SkMatrix m = self.getTotalMatrix();
                          m.get9(nineMatrixValues);
                      }))
            .function(
                    "_makeSurface",
                    optional_override([](SkCanvas& self, SimpleImageInfo sii) -> sk_sp<SkSurface> {
                        return self.makeSurface(toSkImageInfo(sii), nullptr);
                    }),
                    allow_raw_pointers())

            .function("_readPixels",
                      optional_override([](SkCanvas& self,
                                           SimpleImageInfo di,
                                           WASMPointerU8 pPtr,
                                           size_t dstRowBytes,
                                           int srcX,
                                           int srcY) {
                          uint8_t* pixels = reinterpret_cast<uint8_t*>(pPtr);
                          SkImageInfo dstInfo = toSkImageInfo(di);

                          return self.readPixels(dstInfo, pixels, dstRowBytes, srcX, srcY);
                      }),
                      allow_raw_pointers())
            .function("restore", &SkCanvas::restore)
            .function("restoreToCount", &SkCanvas::restoreToCount)
            .function("rotate", select_overload<void(float, float, float)>(&SkCanvas::rotate))
            .function("save", &SkCanvas::save)
            .function("_saveLayer",
                      optional_override([](SkCanvas& self,
                                           const SkPaint* p,
                                           WASMPointerF32 fPtr,
                                           const SkImageFilter* backdrop,
                                           SkCanvas::SaveLayerFlags flags,
                                           SkTileMode backdropFilterTileMode) -> int {
                          SkRect* bounds = reinterpret_cast<SkRect*>(fPtr);
                          return self.saveLayer(SkCanvas::SaveLayerRec(
                                  bounds, p, backdrop, backdropFilterTileMode, nullptr, flags));
                      }),
                      allow_raw_pointers())
            .function("saveLayerPaint",
                      optional_override([](SkCanvas& self, const SkPaint& p) -> int {
                          return self.saveLayer(SkCanvas::SaveLayerRec(nullptr, &p, 0));
                      }))
            .function("scale", &SkCanvas::scale)
            .function("skew", &SkCanvas::skew)
            .function("translate", &SkCanvas::translate)
            .function("_writePixels",
                      optional_override([](SkCanvas& self,
                                           SimpleImageInfo di,
                                           WASMPointerU8 pPtr,
                                           size_t srcRowBytes,
                                           int dstX,
                                           int dstY) {
                          uint8_t* pixels = reinterpret_cast<uint8_t*>(pPtr);
                          SkImageInfo dstInfo = toSkImageInfo(di);

                          return self.writePixels(dstInfo, pixels, srcRowBytes, dstX, dstY);
                      }));

    class_<SkColorFilter>("ColorFilter")
            .smart_ptr<sk_sp<SkColorFilter>>("sk_sp<ColorFilter>>")
            .class_function(
                    "_MakeBlend",
                    optional_override([](WASMPointerF32 cPtr,
                                         SkBlendMode mode,
                                         sk_sp<SkColorSpace> colorSpace) -> sk_sp<SkColorFilter> {
                        return SkColorFilters::Blend(ptrToSkColor4f(cPtr), colorSpace, mode);
                    }))
            .class_function("MakeCompose", &SkColorFilters::Compose)
            .class_function("MakeLerp", &SkColorFilters::Lerp)
            .class_function("MakeLinearToSRGBGamma", &SkColorFilters::LinearToSRGBGamma)
            .class_function("_makeMatrix", optional_override([](WASMPointerF32 fPtr) {
                                float* twentyFloats = reinterpret_cast<float*>(fPtr);
                                return SkColorFilters::Matrix(twentyFloats);
                            }))
            .class_function("MakeSRGBToLinearGamma", &SkColorFilters::SRGBToLinearGamma)
            .class_function("MakeLuma", &SkLumaColorFilter::Make);

    class_<SkContourMeasureIter>("ContourMeasureIter")
            .constructor<const SkPath&, bool, float>()
            .function("next", &SkContourMeasureIter::next);

    class_<SkContourMeasure>("ContourMeasure")
            .smart_ptr<sk_sp<SkContourMeasure>>("sk_sp<ContourMeasure>>")
            .function("_getPosTan",
                      optional_override([](SkContourMeasure& self,
                                           float distance,
                                           WASMPointerF32 oPtr) -> void {
                          SkPoint* pointAndVector = reinterpret_cast<SkPoint*>(oPtr);
                          if (!self.getPosTan(distance, pointAndVector, pointAndVector + 1)) {
                              SkDebugf("zero-length path in getPosTan\n");
                          }
                      }))
            .function("getSegment",
                      optional_override([](SkContourMeasure& self,
                                           float startD,
                                           float stopD,
                                           bool startWithMoveTo) -> SkPath {
                          SkPath p;
                          bool ok = self.getSegment(startD, stopD, &p, startWithMoveTo);
                          if (ok) {
                              return p;
                          }
                          return SkPath();
                      }))
            .function("isClosed", &SkContourMeasure::isClosed)
            .function("length", &SkContourMeasure::length);

#ifndef CK_NO_FONTS
    class_<SkFont>("Font")
            .constructor<>()
            .constructor<sk_sp<SkTypeface>>()
            .constructor<sk_sp<SkTypeface>, float>()
            .constructor<sk_sp<SkTypeface>, float, float, float>()
            .function("_getGlyphWidthBounds",
                      optional_override([](SkFont& self,
                                           WASMPointerU16 gPtr,
                                           int numGlyphs,
                                           WASMPointerF32 wPtr,
                                           WASMPointerF32 rPtr,
                                           SkPaint* paint) {
                          const SkGlyphID* glyphs = reinterpret_cast<const SkGlyphID*>(gPtr);
                          // On the JS side only one of these is set at a time for easier
                          // ergonomics.
                          SkRect* outputRects = reinterpret_cast<SkRect*>(rPtr);
                          float* outputWidths = reinterpret_cast<float*>(wPtr);
                          self.getWidthsBounds({glyphs, numGlyphs},
                                               {outputWidths, outputWidths ? numGlyphs : 0},
                                               {outputRects, outputRects ? numGlyphs : 0},
                                               paint);
                      }),
                      allow_raw_pointers())
            .function("_getGlyphIDs",
                      optional_override([](SkFont& self,
                                           WASMPointerU8 sptr,
                                           size_t strLen,
                                           size_t expectedCodePoints,
                                           WASMPointerU16 iPtr) -> int {
                          char* str = reinterpret_cast<char*>(sptr);
                          SkGlyphID* glyphIDs = reinterpret_cast<SkGlyphID*>(iPtr);

                          int actualCodePoints =
                                  self.textToGlyphs(str,
                                                    strLen,
                                                    SkTextEncoding::kUTF8,
                                                    {glyphIDs, glyphIDs ? expectedCodePoints : 0});
                          return actualCodePoints;
                      }))
            .function("getMetrics", optional_override([](SkFont& self) -> JSObject {
                          SkFontMetrics fm;
                          self.getMetrics(&fm);

                          JSObject j = emscripten::val::object();
                          j.set("ascent", fm.fAscent);
                          j.set("descent", fm.fDescent);
                          j.set("leading", fm.fLeading);
                          if (!(fm.fFlags & SkFontMetrics::kBoundsInvalid_Flag)) {
                              const float rect[] = {fm.fXMin, fm.fTop, fm.fXMax, fm.fBottom};
                              j.set("bounds", MakeTypedArray(4, rect));
                          }
                          return j;
                      }))
            .function("_getGlyphIntercepts",
                      optional_override([](SkFont& self,
                                           WASMPointerU16 gPtr,
                                           size_t numGlyphs,
                                           bool ownGlyphs,
                                           WASMPointerF32 pPtr,
                                           size_t numPos,
                                           bool ownPos,
                                           float top,
                                           float bottom) -> Float32Array {
                          JSSpan<uint16_t> glyphs(gPtr, numGlyphs, ownGlyphs);
                          JSSpan<float> pos(pPtr, numPos, ownPos);
                          if (glyphs.size() > (pos.size() >> 1)) {
                              return emscripten::val("Not enough x,y position pairs for glyphs");
                          }
                          auto sects = self.getIntercepts(
                                  glyphs, {(const SkPoint*)pos.data(), numGlyphs}, top, bottom);
                          return MakeTypedArray(sects.size(), (const float*)sects.data());
                      }),
                      allow_raw_pointers())
            .function("getScaleX", &SkFont::getScaleX)
            .function("getSize", &SkFont::getSize)
            .function("getSkewX", &SkFont::getSkewX)
            .function("isEmbolden", &SkFont::isEmbolden)
            .function("getTypeface", &SkFont::getTypeface, allow_raw_pointers())
            .function("setEdging", &SkFont::setEdging)
            .function("setEmbeddedBitmaps", &SkFont::setEmbeddedBitmaps)
            .function("setHinting", &SkFont::setHinting)
            .function("setLinearMetrics", &SkFont::setLinearMetrics)
            .function("setScaleX", &SkFont::setScaleX)
            .function("setSize", &SkFont::setSize)
            .function("setSkewX", &SkFont::setSkewX)
            .function("setEmbolden", &SkFont::setEmbolden)
            .function("setSubpixel", &SkFont::setSubpixel)
            .function("setTypeface", &SkFont::setTypeface, allow_raw_pointers());

    class_<SkFontMgr>("FontMgr")
            .smart_ptr<sk_sp<SkFontMgr>>("sk_sp<FontMgr>")
            .class_function(
                    "_fromData",
                    optional_override([](WASMPointerU32 dPtr,
                                         WASMPointerU32 sPtr,
                                         int numFonts) -> sk_sp<SkFontMgr> {
                        auto datas = reinterpret_cast<const uint8_t**>(dPtr);
                        auto sizes = reinterpret_cast<const size_t*>(sPtr);

                        std::unique_ptr<sk_sp<SkData>[]> skdatas(new sk_sp<SkData>[numFonts]);
                        for (int i = 0; i < numFonts; ++i) {
                            skdatas[i] = SkData::MakeFromMalloc(datas[i], sizes[i]);
                        }

                        return SkFontMgr_New_Custom_Data(SkSpan(skdatas.get(), numFonts));
                    }),
                    allow_raw_pointers())
            .function("countFamilies", &SkFontMgr::countFamilies)
            .function("getFamilyName",
                      optional_override([](SkFontMgr& self, int index) -> JSString {
                          if (index < 0 || index >= self.countFamilies()) {
                              return emscripten::val::null();
                          }
                          SkString s;
                          self.getFamilyName(index, &s);
                          return emscripten::val(s.c_str());
                      }))
            .function("matchFamilyStyle",
                      optional_override([](SkFontMgr& self,
                                           std::string name,
                                           emscripten::val jsFontStyle) -> sk_sp<SkTypeface> {
                          auto weight =
                                  SkFontStyle::Weight(jsFontStyle["weight"].isUndefined()
                                                              ? SkFontStyle::kNormal_Weight
                                                              : jsFontStyle["weight"].as<int>());
                          auto width = SkFontStyle::Width(jsFontStyle["width"].isUndefined()
                                                                  ? SkFontStyle::kNormal_Width
                                                                  : jsFontStyle["width"].as<int>());
                          auto slant = SkFontStyle::Slant(
                                  jsFontStyle["slant"].isUndefined()
                                          ? SkFontStyle::kUpright_Slant
                                          : static_cast<SkFontStyle::Slant>(
                                                    jsFontStyle["slant"].as<int>()));

                          SkFontStyle style(weight, width, slant);

                          return self.matchFamilyStyle(name.c_str(), style);
                      }),
                      allow_raw_pointers())
#ifdef SK_DEBUG
            .function("dumpFamilies", optional_override([](SkFontMgr& self) {
                          int numFam = self.countFamilies();
                          SkDebugf("There are %d font families\n", numFam);
                          for (int i = 0; i < numFam; i++) {
                              SkString s;
                              self.getFamilyName(i, &s);
                              SkDebugf("\t%s\n", s.c_str());
                          }
                      }))
#endif
            .function(
                    "_makeTypefaceFromData",
                    optional_override(
                            [](SkFontMgr& self, WASMPointerU8 fPtr, int flen) -> sk_sp<SkTypeface> {
                                uint8_t* font = reinterpret_cast<uint8_t*>(fPtr);
                                sk_sp<SkData> fontData = SkData::MakeFromMalloc(font, flen);

                                return self.makeFromData(fontData);
                            }),
                    allow_raw_pointers());
#endif  // CK_NO_FONTS

    class_<SkImage>("Image")
            .smart_ptr<sk_sp<SkImage>>("sk_sp<Image>")
#ifdef CK_ENABLE_WEBGL
            .class_function("_makeFromGenerator", &MakeImageFromGenerator)
#endif
            // Note that this needs to be cleaned up with delete().
            .function("getColorSpace",
                      optional_override([](sk_sp<SkImage> self) -> sk_sp<SkColorSpace> {
                          return self->imageInfo().refColorSpace();
                      }),
                      allow_raw_pointers())
            .function("getImageInfo", optional_override([](sk_sp<SkImage> self) -> JSObject {
                          // We cannot return a SimpleImageInfo because the colorspace object would
                          // be leaked.
                          JSObject result = emscripten::val::object();
                          SkImageInfo ii = self->imageInfo();
                          result.set("alphaType", ii.alphaType());
                          result.set("colorType", ii.colorType());
                          result.set("height", ii.height());
                          result.set("width", ii.width());
                          return result;
                      }))
            .function("height", &SkImage::height)
            .function("_encodeToBytes",
                      optional_override([](sk_sp<SkImage> self,
                                           SkEncodedImageFormat fmt,
                                           int quality) -> Uint8Array {
                          return encodeImage(nullptr, self, fmt, quality);
                      }))
#if defined(ENABLE_GPU)
            .function("_encodeToBytes",
                      optional_override([](sk_sp<SkImage> self,
                                           SkEncodedImageFormat fmt,
                                           int quality,
                                           GrDirectContext* dContext) -> Uint8Array {
                          return encodeImage(dContext, self, fmt, quality);
                      }),
                      allow_raw_pointers())
#endif
            .function("makeCopyWithDefaultMipmaps",
                      optional_override([](sk_sp<SkImage> self) -> sk_sp<SkImage> {
                          return self->withDefaultMipmaps();
                      }))
            .function("_makeShaderCubic",
                      optional_override([](sk_sp<SkImage> self,
                                           SkTileMode tx,
                                           SkTileMode ty,
                                           float B,
                                           float C,  // See SkSamplingOptions.h for docs.
                                           WASMPointerF32 mPtr) -> sk_sp<SkShader> {
                          OptionalMatrix localMatrix(mPtr);
                          return self->makeShader(
                                  tx, ty, SkSamplingOptions({B, C}), mPtr ? &localMatrix : nullptr);
                      }),
                      allow_raw_pointers())
            .function("_makeShaderOptions",
                      optional_override([](sk_sp<SkImage> self,
                                           SkTileMode tx,
                                           SkTileMode ty,
                                           SkFilterMode filter,
                                           SkMipmapMode mipmap,
                                           WASMPointerF32 mPtr) -> sk_sp<SkShader> {
                          OptionalMatrix localMatrix(mPtr);
                          return self->makeShader(
                                  tx, ty, {filter, mipmap}, mPtr ? &localMatrix : nullptr);
                      }),
                      allow_raw_pointers())
#if defined(ENABLE_GPU)
            .function("_readPixels",
                      optional_override([](sk_sp<SkImage> self,
                                           SimpleImageInfo sii,
                                           WASMPointerU8 pPtr,
                                           size_t dstRowBytes,
                                           int srcX,
                                           int srcY,
                                           GrDirectContext* dContext) -> bool {
                          uint8_t* pixels = reinterpret_cast<uint8_t*>(pPtr);
                          SkImageInfo ii = toSkImageInfo(sii);
                          return self->readPixels(dContext, ii, pixels, dstRowBytes, srcX, srcY);
                      }),
                      allow_raw_pointers())
#endif
            .function("_readPixels",
                      optional_override([](sk_sp<SkImage> self,
                                           SimpleImageInfo sii,
                                           WASMPointerU8 pPtr,
                                           size_t dstRowBytes,
                                           int srcX,
                                           int srcY) -> bool {
                          uint8_t* pixels = reinterpret_cast<uint8_t*>(pPtr);
                          SkImageInfo ii = toSkImageInfo(sii);
                          return self->readPixels(nullptr, ii, pixels, dstRowBytes, srcX, srcY);
                      }),
                      allow_raw_pointers())
            .function("width", &SkImage::width);

    class_<SkImageFilter>("ImageFilter")
            .smart_ptr<sk_sp<SkImageFilter>>("sk_sp<ImageFilter>")
            .function("_getOutputBounds",
                      optional_override([](const SkImageFilter& self,
                                           WASMPointerF32 bPtr,
                                           WASMPointerF32 mPtr,
                                           WASMPointerU32 oPtr) -> void {
                          SkRect* rect = reinterpret_cast<SkRect*>(bPtr);
                          OptionalMatrix ctm(mPtr);
                          SkIRect* output = reinterpret_cast<SkIRect*>(oPtr);
                          output[0] = self.filterBounds(ctm.mapRect(*rect).roundOut(),
                                                        ctm,
                                                        SkImageFilter::kForward_MapDirection);
                      }))
            .class_function(
                    "MakeBlend",
                    optional_override([](SkBlendMode mode,
                                         sk_sp<SkImageFilter> background,
                                         sk_sp<SkImageFilter> foreground) -> sk_sp<SkImageFilter> {
                        return SkImageFilters::Blend(mode, background, foreground);
                    }))
            .class_function(
                    "MakeBlur",
                    optional_override([](float sigmaX,
                                         float sigmaY,
                                         SkTileMode tileMode,
                                         sk_sp<SkImageFilter> input) -> sk_sp<SkImageFilter> {
                        return SkImageFilters::Blur(sigmaX, sigmaY, tileMode, input);
                    }))
            .class_function(
                    "MakeColorFilter",
                    optional_override([](sk_sp<SkColorFilter> cf,
                                         sk_sp<SkImageFilter> input) -> sk_sp<SkImageFilter> {
                        return SkImageFilters::ColorFilter(cf, input);
                    }))
            .class_function("MakeCompose", &SkImageFilters::Compose)
            .class_function(
                    "MakeDilate",
                    optional_override([](float radiusX,
                                         float radiusY,
                                         sk_sp<SkImageFilter> input) -> sk_sp<SkImageFilter> {
                        return SkImageFilters::Dilate(radiusX, radiusY, input);
                    }))
            .class_function(
                    "MakeDisplacementMap",
                    optional_override([](SkColorChannel xChannelSelector,
                                         SkColorChannel yChannelSelector,
                                         float scale,
                                         sk_sp<SkImageFilter> displacement,
                                         sk_sp<SkImageFilter> color) -> sk_sp<SkImageFilter> {
                        return SkImageFilters::DisplacementMap(
                                xChannelSelector, yChannelSelector, scale, displacement, color);
                    }))
            .class_function("MakeShader",
                            optional_override([](sk_sp<SkShader> shader) -> sk_sp<SkImageFilter> {
                                return SkImageFilters::Shader(shader);
                            }))
            .class_function(
                    "_MakeDropShadow",
                    optional_override([](float dx,
                                         float dy,
                                         float sigmaX,
                                         float sigmaY,
                                         WASMPointerF32 cPtr,
                                         sk_sp<SkImageFilter> input) -> sk_sp<SkImageFilter> {
                        SkColor4f c = ptrToSkColor4f(cPtr);
                        return SkImageFilters::DropShadow(
                                dx, dy, sigmaX, sigmaY, c.toSkColor(), input);
                    }))
            .class_function(
                    "_MakeDropShadowOnly",
                    optional_override([](float dx,
                                         float dy,
                                         float sigmaX,
                                         float sigmaY,
                                         WASMPointerF32 cPtr,
                                         sk_sp<SkImageFilter> input) -> sk_sp<SkImageFilter> {
                        SkColor4f c = ptrToSkColor4f(cPtr);
                        return SkImageFilters::DropShadowOnly(
                                dx, dy, sigmaX, sigmaY, c.toSkColor(), input);
                    }))
            .class_function(
                    "MakeErode",
                    optional_override([](float radiusX,
                                         float radiusY,
                                         sk_sp<SkImageFilter> input) -> sk_sp<SkImageFilter> {
                        return SkImageFilters::Erode(radiusX, radiusY, input);
                    }))
            .class_function("_MakeImageCubic",
                            optional_override([](sk_sp<SkImage> image,
                                                 float B,
                                                 float C,
                                                 WASMPointerF32 srcPtr,
                                                 WASMPointerF32 dstPtr) -> sk_sp<SkImageFilter> {
                                const SkRect* src = reinterpret_cast<const SkRect*>(srcPtr);
                                const SkRect* dst = reinterpret_cast<const SkRect*>(dstPtr);
                                if (src && dst) {
                                    return SkImageFilters::Image(
                                            image, *src, *dst, SkSamplingOptions({B, C}));
                                }
                                return SkImageFilters::Image(image, SkSamplingOptions({B, C}));
                            }))
            .class_function("_MakeImageOptions",
                            optional_override([](sk_sp<SkImage> image,
                                                 SkFilterMode fm,
                                                 SkMipmapMode mm,
                                                 WASMPointerF32 srcPtr,
                                                 WASMPointerF32 dstPtr) -> sk_sp<SkImageFilter> {
                                const SkRect* src = reinterpret_cast<const SkRect*>(srcPtr);
                                const SkRect* dst = reinterpret_cast<const SkRect*>(dstPtr);
                                if (src && dst) {
                                    return SkImageFilters::Image(
                                            image, *src, *dst, SkSamplingOptions(fm, mm));
                                }
                                return SkImageFilters::Image(image, SkSamplingOptions(fm, mm));
                            }))
            .class_function(
                    "_MakeMatrixTransformCubic",
                    optional_override([](WASMPointerF32 mPtr,
                                         float B,
                                         float C,
                                         sk_sp<SkImageFilter> input) -> sk_sp<SkImageFilter> {
                        OptionalMatrix matr(mPtr);
                        return SkImageFilters::MatrixTransform(
                                matr, SkSamplingOptions({B, C}), input);
                    }))
            .class_function(
                    "_MakeMatrixTransformOptions",
                    optional_override([](WASMPointerF32 mPtr,
                                         SkFilterMode fm,
                                         SkMipmapMode mm,
                                         sk_sp<SkImageFilter> input) -> sk_sp<SkImageFilter> {
                        OptionalMatrix matr(mPtr);
                        return SkImageFilters::MatrixTransform(
                                matr, SkSamplingOptions(fm, mm), input);
                    }))
            .class_function("MakeOffset",
                            optional_override([](float dx, float dy, sk_sp<SkImageFilter> input)
                                                      -> sk_sp<SkImageFilter> {
                                return SkImageFilters::Offset(dx, dy, input);
                            }));

    class_<SkMaskFilter>("MaskFilter")
            .smart_ptr<sk_sp<SkMaskFilter>>("sk_sp<MaskFilter>")
            .class_function("MakeBlur",
                            optional_override([](SkBlurStyle style,
                                                 float sigma,
                                                 bool respectCTM) -> sk_sp<SkMaskFilter> {
                                // Adds a little helper because emscripten doesn't expose default
                                // params.
                                return SkMaskFilter::MakeBlur(style, sigma, respectCTM);
                            }),
                            allow_raw_pointers());

    class_<SkPaint>("Paint")
            .constructor<>()
            .function("copy", optional_override([](const SkPaint& self) -> SkPaint {
                          SkPaint p(self);
                          return p;
                      }))
            // provide an allocated place to put the returned color
            .function("_getColor",
                      optional_override([](SkPaint& self, WASMPointerF32 cPtr) -> void {
                          const SkColor4f& c = self.getColor4f();
                          float* fourFloats = reinterpret_cast<float*>(cPtr);
                          memcpy(fourFloats, c.vec(), 4 * sizeof(float));
                      }))
            .function("getStrokeCap", &SkPaint::getStrokeCap)
            .function("getStrokeJoin", &SkPaint::getStrokeJoin)
            .function("getStrokeMiter", &SkPaint::getStrokeMiter)
            .function("getStrokeWidth", &SkPaint::getStrokeWidth)
            .function("setAntiAlias", &SkPaint::setAntiAlias)
            .function("setAlphaf", &SkPaint::setAlphaf)
            .function("setBlendMode", &SkPaint::setBlendMode)
            .function("setBlender", &SkPaint::setBlender)
            .function(
                    "_setColor",
                    optional_override(
                            [](SkPaint& self, WASMPointerF32 cPtr, sk_sp<SkColorSpace> colorSpace) {
                                self.setColor(ptrToSkColor4f(cPtr), colorSpace.get());
                            }))
            .function("setColorInt", optional_override([](SkPaint& self, SkColor color) {
                          self.setColor(SkColor4f::FromColor(color), nullptr);
                      }))
            .function("setColorInt",
                      optional_override(
                              [](SkPaint& self, SkColor color, sk_sp<SkColorSpace> colorSpace) {
                                  self.setColor(SkColor4f::FromColor(color), colorSpace.get());
                              }))
            .function("setColorFilter", &SkPaint::setColorFilter)
            .function("setDither", &SkPaint::setDither)
            .function("setImageFilter", &SkPaint::setImageFilter)
            .function("setMaskFilter", &SkPaint::setMaskFilter)
            .function("setPathEffect", &SkPaint::setPathEffect)
            .function("setShader", &SkPaint::setShader)
            .function("setStrokeCap", &SkPaint::setStrokeCap)
            .function("setStrokeJoin", &SkPaint::setStrokeJoin)
            .function("setStrokeMiter", &SkPaint::setStrokeMiter)
            .function("setStrokeWidth", &SkPaint::setStrokeWidth)
            .function("setStyle", &SkPaint::setStyle);

    class_<SkColorSpace>("ColorSpace")
            .smart_ptr<sk_sp<SkColorSpace>>("sk_sp<ColorSpace>")
            .class_function(
                    "Equals",
                    optional_override([](sk_sp<SkColorSpace> a, sk_sp<SkColorSpace> b) -> bool {
                        return SkColorSpace::Equals(a.get(), b.get());
                    }))
            // These are private because they are to be called once in interface.js to
            // avoid clients having to delete the returned objects.
            .class_function("_MakeSRGB", &SkColorSpace::MakeSRGB)
            .class_function("_MakeDisplayP3", optional_override([]() -> sk_sp<SkColorSpace> {
                                return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB,
                                                             SkNamedGamut::kDisplayP3);
                            }))
            .class_function("_MakeAdobeRGB", optional_override([]() -> sk_sp<SkColorSpace> {
                                return SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2,
                                                             SkNamedGamut::kAdobeRGB);
                            }));

    class_<SkPathEffect>("PathEffect")
            .smart_ptr<sk_sp<SkPathEffect>>("sk_sp<PathEffect>")
            .class_function("MakeCorner", &SkCornerPathEffect::Make)
            .class_function(
                    "_MakeDash",
                    optional_override(
                            [](WASMPointerF32 cptr, int count, float phase) -> sk_sp<SkPathEffect> {
                                const float* intervals = reinterpret_cast<const float*>(cptr);
                                return SkDashPathEffect::Make({intervals, count}, phase);
                            }),
                    allow_raw_pointers())
            .class_function("MakeDiscrete", &SkDiscretePathEffect::Make)
            .class_function(
                    "_MakeLine2D",
                    optional_override([](float width, WASMPointerF32 mPtr) -> sk_sp<SkPathEffect> {
                        SkMatrix matrix;
                        const float* nineMatrixValues = reinterpret_cast<const float*>(mPtr);
                        matrix.set9(nineMatrixValues);
                        return SkLine2DPathEffect::Make(width, matrix);
                    }),
                    allow_raw_pointers())
            .class_function("MakePath1D", &SkPath1DPathEffect::Make)
            .class_function(
                    "_MakePath2D",
                    optional_override([](WASMPointerF32 mPtr, SkPath path) -> sk_sp<SkPathEffect> {
                        SkMatrix matrix;
                        const float* nineMatrixValues = reinterpret_cast<const float*>(mPtr);
                        matrix.set9(nineMatrixValues);
                        return SkPath2DPathEffect::Make(matrix, path);
                    }),
                    allow_raw_pointers());

    class_<SkPath>("Path")
            .constructor<>()
            .class_function("CanInterpolate", &CanInterpolate)
#ifdef CK_INCLUDE_PATHOPS
            .class_function("MakeFromOp", &MakePathFromOp)
#endif
            .class_function("MakeFromSVGString", &MakePathFromSVGString)
            .class_function("MakeFromPathInterpolation", &MakePathFromInterpolation)
            .class_function("_MakeFromCmds", &MakePathFromCmds)
            .class_function("_MakeFromVerbsPointsWeights", &MakePathFromVerbsPointsWeights)
            .function("countPoints", &SkPath::countPoints)
            .function("contains", optional_override(
                              [](const SkPath& self, float x, float y) -> bool {
                                  return self.contains({x, y});
                              }))
            .function("_getPoint",
                      optional_override(
                              [](const SkPath& self, int index, WASMPointerF32 oPtr) -> void {
                                  SkPoint* output = reinterpret_cast<SkPoint*>(oPtr);
                                  *output = self.getPoint(index);
                              }))
            .function("isEmpty", &SkPath::isEmpty)

            .function("makeDashed", &MakeDashed)
            .function("_makeTrimmed", &MakeTrimmed)
            .function("_makeStroked", &MakeStroked)

            // Exporting
            .function("toSVGString", &ToSVGString)
            .function("toCmds", &ToCmds)

            .function("setFillType", &SkPath::setFillType)
            .function("getFillType", &SkPath::getFillType)
            .function("_getBounds",
                      optional_override([](const SkPath& self, WASMPointerF32 fPtr) -> void {
                          SkRect* output = reinterpret_cast<SkRect*>(fPtr);
                          output[0] = self.getBounds();
                      }))
            .function("_computeTightBounds",
                      optional_override([](const SkPath& self, WASMPointerF32 fPtr) -> void {
                          SkRect* output = reinterpret_cast<SkRect*>(fPtr);
                          output[0] = self.computeTightBounds();
                      }))
            .function("equals", &Equals)
            .function("copy", &CopyPath)
#ifdef SK_DEBUG
            .function("dump", select_overload<void() const>(&SkPath::dump))
            .function("dumpHex", select_overload<void() const>(&SkPath::dumpHex))
#endif
#ifdef CK_INCLUDE_PATHOPS
            .function("makeAsWinding", &MakeAsWinding)
            .function("_makeCombined",
                      optional_override([](const SkPath& self,
                                           const SkPath& other,
                                           SkPathOp op) -> SkPathOrNull {
                          return MakePathFromOp(self, other, op);
                      }))
            .function("_makeSimplified", &MakeSimplified)
#endif
            ;

    class_<SkPathBuilder>("PathBuilder")
            .constructor<>()
            .constructor<SkPath>()
            // These methods all return void and we handle the "chaining" logic in the JS.
            // If we returned PathBuilder here, emsdk would allocate new pathbuilder objects
            // causing memory leaks.
            .function("_addArc",
                      optional_override([](SkPathBuilder& self,
                                           WASMPointerF32 fPtr,
                                           float startAngle,
                                           float sweepAngle) -> void {
                          const SkRect* oval = reinterpret_cast<const SkRect*>(fPtr);
                          self.addArc(*oval, startAngle, sweepAngle);
                      }))
            .function("_addOval",
                      optional_override([](SkPathBuilder& self,
                                           WASMPointerF32 fPtr,
                                           bool ccw,
                                           unsigned start) -> void {
                          const SkRect* oval = reinterpret_cast<const SkRect*>(fPtr);
                          self.addOval(
                                  *oval, ccw ? SkPathDirection::kCCW : SkPathDirection::kCW, start);
                      }))
            .function("_addCircle",
                      optional_override([](SkPathBuilder& self, float x, float y, float r, bool ccw)
                                                -> void {
                          self.addCircle(
                                  x, y, r, ccw ? SkPathDirection::kCCW : SkPathDirection::kCW);
                      }))
            // interface.js has 3 overloads of addPath
            .function("_addPath", &ApplyAddPath)
            .function("_addPolygon",
                      optional_override([](SkPathBuilder& self,
                                           WASMPointerF32 fPtr,
                                           int count,
                                           bool close) -> void {
                          const SkPoint* pts = reinterpret_cast<const SkPoint*>(fPtr);
                          self.addPolygon({pts, count}, close);
                      }))
            .function("_addRect",
                      optional_override(
                              [](SkPathBuilder& self, WASMPointerF32 fPtr, bool ccw) -> void {
                                  const SkRect* rect = reinterpret_cast<const SkRect*>(fPtr);
                                  self.addRect(*rect,
                                               ccw ? SkPathDirection::kCCW : SkPathDirection::kCW);
                              }))
            .function("_addRRect",
                      optional_override(
                              [](SkPathBuilder& self, WASMPointerF32 fPtr, bool ccw) -> void {
                                  self.addRRect(ptrToSkRRect(fPtr),
                                                ccw ? SkPathDirection::kCCW : SkPathDirection::kCW);
                              }))
            .function("_addVerbsPointsWeights", &PathAddVerbsPointsWeights)
            .function("_arcToOval",
                      optional_override([](SkPathBuilder& self,
                                           WASMPointerF32 fPtr,
                                           float startAngle,
                                           float sweepAngle,
                                           bool forceMoveTo) -> void {
                          const SkRect* oval = reinterpret_cast<const SkRect*>(fPtr);
                          self.arcTo(*oval, startAngle, sweepAngle, forceMoveTo);
                      }))
            .function("_arcToRotated", &ApplyArcToArcSize)
            .function("_arcToTangent", &ApplyArcToTangent)
            .function("_close", &ApplyClose)
            .function("_conicTo", &ApplyConicTo)
            .function("contains", optional_override(
                              [](const SkPathBuilder& self, float x, float y) -> bool {
                                  return self.contains({x, y});
                              }))
            .function("countPoints", &SkPathBuilder::countPoints)
            .function("_cubicTo", &ApplyCubicTo)
            .function("detach", optional_override([](SkPathBuilder& self) -> SkPath {
                          return self.detach();
                      }))
            .function("_getBounds", optional_override(
                      [](const SkPathBuilder& self, WASMPointerF32 fPtr) -> void {
                        SkRect* output = reinterpret_cast<SkRect*>(fPtr);
                        if (auto bounds = self.computeFiniteBounds()) {
                            output[0] = *bounds;
                            return;
                        }
                        output[0] = SkRect::MakeEmpty();
                      }))
            .function("isEmpty", &SkPathBuilder::isEmpty)
            .function("_lineTo", &ApplyLineTo)
            .function("_moveTo", &ApplyMoveTo)
            .function("_quadTo", &ApplyQuadTo)
            .function("_rArcTo", &ApplyRArcToArcSize)
            .function("_rConicTo", &ApplyRConicTo)
            .function("_rCubicTo", &ApplyRCubicTo)
            .function("_rLineTo", &ApplyRLineTo)
            .function("_rMoveTo", &ApplyRMoveTo)
            .function("_rQuadTo", &ApplyRQuadTo)
            .function("reset", &ApplyReset)
            .function("setFillType", &SkPathBuilder::setFillType)
            .function("snapshot", optional_override([](SkPathBuilder& self) -> SkPath {
                          return self.snapshot();
                      }))
            .function("_transform",
                      select_overload<void(SkPathBuilder&,
                                           float,
                                           float,
                                           float,
                                           float,
                                           float,
                                           float,
                                           float,
                                           float,
                                           float)>(&ApplyTransform));

    static SkRTreeFactory bbhFactory;
    class_<SkPictureRecorder>("PictureRecorder")
            .constructor<>()
            .function("_beginRecording",
                      optional_override([](SkPictureRecorder& self,
                                           WASMPointerF32 fPtr,
                                           bool computeBounds) -> SkCanvas* {
                          SkRect* bounds = reinterpret_cast<SkRect*>(fPtr);
                          return self.beginRecording(*bounds,
                                                     computeBounds ? &bbhFactory : nullptr);
                      }),
                      allow_raw_pointers())
            .function("finishRecordingAsPicture",
                      optional_override([](SkPictureRecorder& self) -> sk_sp<SkPicture> {
                          return self.finishRecordingAsPicture();
                      }),
                      allow_raw_pointers());

    class_<SkPicture>("Picture")
            .smart_ptr<sk_sp<SkPicture>>("sk_sp<Picture>")
            .function("_makeShader",
                      optional_override([](SkPicture& self,
                                           SkTileMode tmx,
                                           SkTileMode tmy,
                                           SkFilterMode mode,
                                           WASMPointerF32 mPtr,
                                           WASMPointerF32 rPtr) -> sk_sp<SkShader> {
                          OptionalMatrix localMatrix(mPtr);
                          SkRect* tileRect = reinterpret_cast<SkRect*>(rPtr);
                          return self.makeShader(
                                  tmx, tmy, mode, mPtr ? &localMatrix : nullptr, tileRect);
                      }),
                      allow_raw_pointers())
            .function("_cullRect",
                      optional_override([](SkPicture& self, WASMPointerF32 fPtr) -> void {
                          SkRect* output = reinterpret_cast<SkRect*>(fPtr);
                          output[0] = self.cullRect();
                      }))
            .function("approximateBytesUsed", &SkPicture::approximateBytesUsed)
#ifdef CK_SERIALIZE_SKP
            // The serialized format of an SkPicture (informally called an "skp"), is not something
            // that clients should ever rely on.  The format may change at anytime and no promises
            // are made for backwards or forward compatibility.
            .function("serialize",
                      optional_override([](SkPicture& self) -> Uint8Array {
                          // We want to make sure we always save the underlying data of the Typeface
                          // to the SkPicture. By default, the data for "system" fonts is not saved,
                          // just an identifier (e.g. the family name and style). We do not want the
                          // user to have to supply a FontMgr with the correct fonts by name when
                          // deserializing, so we choose to always serialize the underlying data.
                          // This makes the SKPs a bit bigger, but easier to use.
                          SkSerialProcs sp;
                          sp.fTypefaceProc = &alwaysSaveTypefaceBytes;
                          sp.fImageProc = [](SkImage* img, void*) -> sk_sp<SkData> {
                              return SkPngEncoder::Encode(nullptr, img, SkPngEncoder::Options{});
                          };

                          sk_sp<SkData> data = self.serialize(&sp);
                          if (!data) {
                              return emscripten::val::null();
                          }
                          return toBytes(data);
                      }),
                      allow_raw_pointers())
#endif
            ;

    class_<SkShader>("Shader")
            .smart_ptr<sk_sp<SkShader>>("sk_sp<Shader>")
            .class_function(
                    "MakeBlend",
                    select_overload<sk_sp<SkShader>(SkBlendMode, sk_sp<SkShader>, sk_sp<SkShader>)>(
                            &SkShaders::Blend))
            .class_function(
                    "_MakeColor",
                    optional_override([](WASMPointerF32 cPtr,
                                         sk_sp<SkColorSpace> colorSpace) -> sk_sp<SkShader> {
                        return SkShaders::Color(ptrToSkColor4f(cPtr), colorSpace);
                    }))
            .class_function("MakeFractalNoise",
                            optional_override([](float baseFreqX,
                                                 float baseFreqY,
                                                 int numOctaves,
                                                 float seed,
                                                 int tileW,
                                                 int tileH) -> sk_sp<SkShader> {
                                // if tileSize is empty (e.g. tileW <= 0 or tileH <= 0, it will be
                                // ignored.
                                SkISize tileSize = SkISize::Make(tileW, tileH);
                                return SkShaders::MakeFractalNoise(
                                        baseFreqX, baseFreqY, numOctaves, seed, &tileSize);
                            }))
            // Here and in other gradient functions, cPtr is a pointer to an array of data
            // representing colors. whether this is an array of SkColor or SkColor4f is indicated
            // by the colorType argument. Only RGBA_8888 and RGBA_F32 are accepted.
            .class_function(
                    "_MakeLinearGradient",
                    optional_override([](WASMPointerF32 fourFloatsPtr,
                                         WASMPointerF32 cPtr,
                                         SkColorType colorType,
                                         WASMPointerF32 pPtr,
                                         int count,
                                         SkTileMode mode,
                                         uint32_t flags,
                                         WASMPointerF32 mPtr,
                                         sk_sp<SkColorSpace> colorSpace) -> sk_sp<SkShader> {
                        const SkPoint* points = reinterpret_cast<const SkPoint*>(fourFloatsPtr);
                        const float* positions = reinterpret_cast<const float*>(pPtr);
                        OptionalMatrix localMatrix(mPtr);

                        if (colorType == SkColorType::kRGBA_F32_SkColorType) {
                            const SkColor4f* colors = reinterpret_cast<const SkColor4f*>(cPtr);
                            return SkGradientShader::MakeLinear(points,
                                                                colors,
                                                                colorSpace,
                                                                positions,
                                                                count,
                                                                mode,
                                                                flags,
                                                                mPtr ? &localMatrix : nullptr);
                        } else if (colorType == SkColorType::kRGBA_8888_SkColorType) {
                            const SkColor* colors = reinterpret_cast<const SkColor*>(cPtr);
                            return SkGradientShader::MakeLinear(points,
                                                                colors,
                                                                positions,
                                                                count,
                                                                mode,
                                                                flags,
                                                                mPtr ? &localMatrix : nullptr);
                        }
                        SkDebugf("%d is not an accepted colorType\n", colorType);
                        return nullptr;
                    }),
                    allow_raw_pointers())
            .class_function(
                    "_MakeRadialGradient",
                    optional_override([](float cx,
                                         float cy,
                                         float radius,
                                         WASMPointerF32 cPtr,
                                         SkColorType colorType,
                                         WASMPointerF32 pPtr,
                                         int count,
                                         SkTileMode mode,
                                         uint32_t flags,
                                         WASMPointerF32 mPtr,
                                         sk_sp<SkColorSpace> colorSpace) -> sk_sp<SkShader> {
                        const float* positions = reinterpret_cast<const float*>(pPtr);
                        OptionalMatrix localMatrix(mPtr);
                        if (colorType == SkColorType::kRGBA_F32_SkColorType) {
                            const SkColor4f* colors = reinterpret_cast<const SkColor4f*>(cPtr);
                            return SkGradientShader::MakeRadial({cx, cy},
                                                                radius,
                                                                colors,
                                                                colorSpace,
                                                                positions,
                                                                count,
                                                                mode,
                                                                flags,
                                                                mPtr ? &localMatrix : nullptr);
                        } else if (colorType == SkColorType::kRGBA_8888_SkColorType) {
                            const SkColor* colors = reinterpret_cast<const SkColor*>(cPtr);
                            return SkGradientShader::MakeRadial({cx, cy},
                                                                radius,
                                                                colors,
                                                                positions,
                                                                count,
                                                                mode,
                                                                flags,
                                                                mPtr ? &localMatrix : nullptr);
                        }
                        SkDebugf("%d is not an accepted colorType\n", colorType);
                        return nullptr;
                    }),
                    allow_raw_pointers())
            .class_function(
                    "_MakeSweepGradient",
                    optional_override([](float cx,
                                         float cy,
                                         WASMPointerF32 cPtr,
                                         SkColorType colorType,
                                         WASMPointerF32 pPtr,
                                         int count,
                                         SkTileMode mode,
                                         float startAngle,
                                         float endAngle,
                                         uint32_t flags,
                                         WASMPointerF32 mPtr,
                                         sk_sp<SkColorSpace> colorSpace) -> sk_sp<SkShader> {
                        const float* positions = reinterpret_cast<const float*>(pPtr);
                        OptionalMatrix localMatrix(mPtr);
                        if (colorType == SkColorType::kRGBA_F32_SkColorType) {
                            const SkColor4f* colors = reinterpret_cast<const SkColor4f*>(cPtr);
                            return SkGradientShader::MakeSweep(cx,
                                                               cy,
                                                               colors,
                                                               colorSpace,
                                                               positions,
                                                               count,
                                                               mode,
                                                               startAngle,
                                                               endAngle,
                                                               flags,
                                                               mPtr ? &localMatrix : nullptr);
                        } else if (colorType == SkColorType::kRGBA_8888_SkColorType) {
                            const SkColor* colors = reinterpret_cast<const SkColor*>(cPtr);
                            return SkGradientShader::MakeSweep(cx,
                                                               cy,
                                                               colors,
                                                               positions,
                                                               count,
                                                               mode,
                                                               startAngle,
                                                               endAngle,
                                                               flags,
                                                               mPtr ? &localMatrix : nullptr);
                        }
                        SkDebugf("%d is not an accepted colorType\n", colorType);
                        return nullptr;
                    }),
                    allow_raw_pointers())
            .class_function("MakeTurbulence",
                            optional_override([](float baseFreqX,
                                                 float baseFreqY,
                                                 int numOctaves,
                                                 float seed,
                                                 int tileW,
                                                 int tileH) -> sk_sp<SkShader> {
                                // if tileSize is empty (e.g. tileW <= 0 or tileH <= 0, it will be
                                // ignored.
                                SkISize tileSize = SkISize::Make(tileW, tileH);
                                return SkShaders::MakeTurbulence(
                                        baseFreqX, baseFreqY, numOctaves, seed, &tileSize);
                            }))
            .class_function(
                    "_MakeTwoPointConicalGradient",
                    optional_override([](WASMPointerF32 fourFloatsPtr,
                                         float startRadius,
                                         float endRadius,
                                         WASMPointerF32 cPtr,
                                         SkColorType colorType,
                                         WASMPointerF32 pPtr,
                                         int count,
                                         SkTileMode mode,
                                         uint32_t flags,
                                         WASMPointerF32 mPtr,
                                         sk_sp<SkColorSpace> colorSpace) -> sk_sp<SkShader> {
                        const SkPoint* startAndEnd =
                                reinterpret_cast<const SkPoint*>(fourFloatsPtr);
                        const float* positions = reinterpret_cast<const float*>(pPtr);
                        OptionalMatrix localMatrix(mPtr);

                        if (colorType == SkColorType::kRGBA_F32_SkColorType) {
                            const SkColor4f* colors = reinterpret_cast<const SkColor4f*>(cPtr);
                            return SkGradientShader::MakeTwoPointConical(
                                    startAndEnd[0],
                                    startRadius,
                                    startAndEnd[1],
                                    endRadius,
                                    colors,
                                    colorSpace,
                                    positions,
                                    count,
                                    mode,
                                    flags,
                                    mPtr ? &localMatrix : nullptr);
                        } else if (colorType == SkColorType::kRGBA_8888_SkColorType) {
                            const SkColor* colors = reinterpret_cast<const SkColor*>(cPtr);
                            return SkGradientShader::MakeTwoPointConical(
                                    startAndEnd[0],
                                    startRadius,
                                    startAndEnd[1],
                                    endRadius,
                                    colors,
                                    positions,
                                    count,
                                    mode,
                                    flags,
                                    mPtr ? &localMatrix : nullptr);
                        }
                        SkDebugf("%d is not an accepted colorType\n", colorType);
                        return nullptr;
                    }),
                    allow_raw_pointers());

#if defined(CK_INCLUDE_RUNTIME_EFFECT)
    class_<SkSL::DebugTrace>("DebugTrace")
            .smart_ptr<sk_sp<SkSL::DebugTrace>>("sk_sp<DebugTrace>")
            .function("writeTrace",
                      optional_override([](const SkSL::DebugTrace* self) -> std::string {
#if defined(CK_DEBUG_TRACE_JSON)
                          SkDynamicMemoryWStream wstream;
                          SkSLTraceUtils::WriteTrace(
                                  static_cast<const SkSL::DebugTracePriv&>(*self), &wstream);
                          sk_sp<SkData> trace = wstream.detachAsData();
                          return std::string(reinterpret_cast<const char*>(trace->bytes()),
                                             trace->size());
#else
                          SkDynamicMemoryWStream wstream;
                          self->dump(&wstream);
                          sk_sp<SkData> trace = wstream.detachAsData();
                          return std::string(reinterpret_cast<const char*>(trace->bytes()),
                                             trace->size());
#endif
                      }),
                      allow_raw_pointers());

    value_object<SkRuntimeEffect::TracedShader>("TracedShader")
            .field("shader", &SkRuntimeEffect::TracedShader::shader)
            .field("debugTrace", &SkRuntimeEffect::TracedShader::debugTrace);

    class_<SkRuntimeEffect>("RuntimeEffect")
            .smart_ptr<sk_sp<SkRuntimeEffect>>("sk_sp<RuntimeEffect>")
            .class_function(
                    "_Make",
                    optional_override([](std::string sksl,
                                         emscripten::val errHandler) -> sk_sp<SkRuntimeEffect> {
                        SkString s(sksl.c_str(), sksl.length());
                        auto [effect, errorText] = SkRuntimeEffect::MakeForShader(s);
                        if (!effect) {
                            errHandler.call<void>("onError", val(errorText.c_str()));
                            return nullptr;
                        }
                        return effect;
                    }))
            .class_function(
                    "_MakeForBlender",
                    optional_override([](std::string sksl,
                                         emscripten::val errHandler) -> sk_sp<SkRuntimeEffect> {
                        SkString s(sksl.c_str(), sksl.length());
                        auto [effect, errorText] = SkRuntimeEffect::MakeForBlender(s);
                        if (!effect) {
                            errHandler.call<void>("onError", val(errorText.c_str()));
                            return nullptr;
                        }
                        return effect;
                    }))
            .class_function("MakeTraced",
                            optional_override([](sk_sp<SkShader> shader,
                                                 int traceCoordX,
                                                 int traceCoordY) -> SkRuntimeEffect::TracedShader {
                                return SkRuntimeEffect::MakeTraced(
                                        shader, SkIPoint::Make(traceCoordX, traceCoordY));
                            }))
            .function("_makeShader",
                      optional_override([](SkRuntimeEffect& self,
                                           WASMPointerF32 fPtr,
                                           size_t fLen,
                                           bool shouldOwnUniforms,
                                           WASMPointerF32 mPtr) -> sk_sp<SkShader> {
                          void* uniformData = reinterpret_cast<void*>(fPtr);
                          castUniforms(uniformData, fLen, self);
                          sk_sp<SkData> uniforms;
                          if (shouldOwnUniforms) {
                              uniforms = SkData::MakeFromMalloc(uniformData, fLen);
                          } else {
                              uniforms = SkData::MakeWithoutCopy(uniformData, fLen);
                          }

                          OptionalMatrix localMatrix(mPtr);
                          return self.makeShader(
                                  uniforms, nullptr, 0, mPtr ? &localMatrix : nullptr);
                      }))
            .function("_makeShaderWithChildren",
                      optional_override([](SkRuntimeEffect& self,
                                           WASMPointerF32 fPtr,
                                           size_t fLen,
                                           bool shouldOwnUniforms,
                                           WASMPointerU32 cPtrs,
                                           size_t cLen,
                                           WASMPointerF32 mPtr) -> sk_sp<SkShader> {
                          void* uniformData = reinterpret_cast<void*>(fPtr);
                          castUniforms(uniformData, fLen, self);
                          sk_sp<SkData> uniforms;
                          if (shouldOwnUniforms) {
                              uniforms = SkData::MakeFromMalloc(uniformData, fLen);
                          } else {
                              uniforms = SkData::MakeWithoutCopy(uniformData, fLen);
                          }

                          sk_sp<SkShader>* children = new sk_sp<SkShader>[cLen];
                          SkShader** childrenPtrs = reinterpret_cast<SkShader**>(cPtrs);
                          for (size_t i = 0; i < cLen; i++) {
                              // This bare pointer was already part of an sk_sp (owned outside of
                              // here), so we want to ref the new sk_sp so makeShader doesn't clean
                              // it up.
                              children[i] = sk_ref_sp<SkShader>(childrenPtrs[i]);
                          }
                          OptionalMatrix localMatrix(mPtr);
                          auto s = self.makeShader(
                                  uniforms, children, cLen, mPtr ? &localMatrix : nullptr);
                          delete[] children;
                          return s;
                      }))
            .function("_makeBlender",
                      optional_override([](SkRuntimeEffect& self,
                                           WASMPointerF32 fPtr,
                                           size_t fLen,
                                           bool shouldOwnUniforms) -> sk_sp<SkBlender> {
                          void* uniformData = reinterpret_cast<void*>(fPtr);
                          castUniforms(uniformData, fLen, self);
                          sk_sp<SkData> uniforms;
                          if (shouldOwnUniforms) {
                              uniforms = SkData::MakeFromMalloc(uniformData, fLen);
                          } else {
                              uniforms = SkData::MakeWithoutCopy(uniformData, fLen);
                          }

                          return self.makeBlender(uniforms, {});
                      }))
            .function("getUniformCount", optional_override([](SkRuntimeEffect& self) -> int {
                          return self.uniforms().size();
                      }))
            .function("getUniformFloatCount", optional_override([](SkRuntimeEffect& self) -> int {
                          return self.uniformSize() / sizeof(float);
                      }))
            .function("getUniformName",
                      optional_override([](SkRuntimeEffect& self, int i) -> JSString {
                          auto it = self.uniforms().begin() + i;
                          return emscripten::val(std::string(it->name).c_str());
                      }))
            .function("getUniform",
                      optional_override([](SkRuntimeEffect& self, int i) -> RuntimeEffectUniform {
                          auto it = self.uniforms().begin() + i;
                          RuntimeEffectUniform su = fromUniform(*it);
                          return su;
                      }));

    value_object<RuntimeEffectUniform>("RuntimeEffectUniform")
            .field("columns", &RuntimeEffectUniform::columns)
            .field("rows", &RuntimeEffectUniform::rows)
            .field("slot", &RuntimeEffectUniform::slot)
            .field("isInteger", &RuntimeEffectUniform::isInteger);

    constant("rt_effect", true);
#endif

    class_<SkSurface>("Surface")
            .smart_ptr<sk_sp<SkSurface>>("sk_sp<Surface>")
            .class_function("_makeRasterDirect",
                            optional_override([](const SimpleImageInfo ii,
                                                 WASMPointerU8 pPtr,
                                                 size_t rowBytes) -> sk_sp<SkSurface> {
                                uint8_t* pixels = reinterpret_cast<uint8_t*>(pPtr);
                                SkImageInfo imageInfo = toSkImageInfo(ii);
                                return SkSurfaces::WrapPixels(imageInfo, pixels, rowBytes, nullptr);
                            }),
                            allow_raw_pointers())
            .function("_flush", optional_override([](SkSurface& self) {
#ifdef CK_ENABLE_WEBGL
                          skgpu::ganesh::FlushAndSubmit(&self);
#endif
                      }))
            .function("_getCanvas", &SkSurface::getCanvas, allow_raw_pointers())
            .function("imageInfo", optional_override([](SkSurface& self) -> SimpleImageInfo {
                          const auto& ii = self.imageInfo();
                          return {ii.width(),
                                  ii.height(),
                                  ii.colorType(),
                                  ii.alphaType(),
                                  ii.refColorSpace()};
                      }))
            .function("height", &SkSurface::height)
#ifdef CK_ENABLE_WEBGL
            .function("_makeImageFromTexture",
                      optional_override([](SkSurface& self,
                                           uint32_t webglHandle,
                                           uint32_t texHandle,
                                           SimpleImageInfo ii) -> sk_sp<SkImage> {
                          auto releaseCtx = new TextureReleaseContext{webglHandle, texHandle};
                          GrGLTextureInfo gti = {
                                  GR_GL_TEXTURE_2D,
                                  texHandle,
                                  GR_GL_RGBA8};  // TODO(kjlubick) look at ii for this
                          auto gbt = GrBackendTextures::MakeGL(
                                  ii.width, ii.height, skgpu::Mipmapped::kNo, gti);
                          auto dContext = GrAsDirectContext(self.getCanvas()->recordingContext());

                          return SkImages::BorrowTextureFrom(
                                  dContext,
                                  gbt,
                                  GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin,
                                  ii.colorType,
                                  ii.alphaType,
                                  ii.colorSpace,
                                  deleteJSTexture,
                                  releaseCtx);
                      }))
#endif  // CK_ENABLE_WEBGL
#ifdef CK_ENABLE_WEBGPU
            .function("_replaceBackendTexture",
                      optional_override([](SkSurface& self,
                                           uint32_t texHandle,
                                           uint32_t texFormat,
                                           int width,
                                           int height) {
                          return ReplaceBackendTexture(self, texHandle, texFormat, width, height);
                      }))
#endif  // CK_ENABLE_WEBGPU
            .function("_makeImageSnapshot",
                      optional_override([](SkSurface& self, WASMPointerU32 iPtr) -> sk_sp<SkImage> {
                          SkIRect* bounds = reinterpret_cast<SkIRect*>(iPtr);
                          if (!bounds) {
                              return self.makeImageSnapshot();
                          }
                          return self.makeImageSnapshot(*bounds);
                      }))
            .function(
                    "_makeSurface",
                    optional_override([](SkSurface& self, SimpleImageInfo sii) -> sk_sp<SkSurface> {
                        return self.makeSurface(toSkImageInfo(sii));
                    }),
                    allow_raw_pointers())
#ifdef ENABLE_GPU
            .function("reportBackendTypeIsGPU", optional_override([](SkSurface& self) -> bool {
                          return self.getCanvas()->recordingContext() != nullptr;
                      }))
            .function("sampleCnt", optional_override([](SkSurface& self) -> int {
                          auto backendRT = SkSurfaces::GetBackendRenderTarget(
                                  &self, SkSurfaces::BackendHandleAccess::kFlushRead);
                          return (backendRT.isValid()) ? backendRT.sampleCnt() : 0;
                      }))
            .function("_resetContext", optional_override([](SkSurface& self) -> void {
                          GrAsDirectContext(self.recordingContext())
                                  ->resetContext(kTextureBinding_GrGLBackendState);
                      }))
#else
            .function("reportBackendTypeIsGPU",
                      optional_override([](SkSurface& self) -> bool { return false; }))
#endif
            .function("width", &SkSurface::width);

#ifndef CK_NO_FONTS
    class_<SkTextBlob>("TextBlob")
            .smart_ptr<sk_sp<SkTextBlob>>("sk_sp<TextBlob>")
            .class_function("_MakeFromRSXform",
                            optional_override([](WASMPointerU8 sptr,
                                                 size_t strBytes,
                                                 WASMPointerF32 xptr,
                                                 const SkFont& font) -> sk_sp<SkTextBlob> {
                                const char* str = reinterpret_cast<const char*>(sptr);
                                // We don't really know how many the client has provided, so we
                                // claim a worst-case value (from text bytes), knowing the impl will
                                // only write as many as needed.
                                SkSpan<const SkRSXform> xforms = {
                                        reinterpret_cast<const SkRSXform*>(xptr), strBytes};

                                return SkTextBlob::MakeFromRSXform(
                                        str, strBytes, xforms, font, SkTextEncoding::kUTF8);
                            }),
                            allow_raw_pointers())
            .class_function("_MakeFromRSXformGlyphs",
                            optional_override([](WASMPointerU16 gPtr,
                                                 size_t byteLen,
                                                 WASMPointerF32 xptr,
                                                 const SkFont& font) -> sk_sp<SkTextBlob> {
                                const size_t numGlyphs = byteLen >> 1;
                                SkSpan<const SkGlyphID> glyphs = {
                                        reinterpret_cast<const SkGlyphID*>(gPtr), numGlyphs};
                                SkSpan<const SkRSXform> xforms = {
                                        reinterpret_cast<const SkRSXform*>(xptr), numGlyphs};

                                return SkTextBlob::MakeFromRSXformGlyphs(glyphs, xforms, font);
                            }),
                            allow_raw_pointers())
            .class_function("_MakeFromText",
                            optional_override([](WASMPointerU8 sptr,
                                                 size_t len,
                                                 const SkFont& font) -> sk_sp<SkTextBlob> {
                                const char* str = reinterpret_cast<const char*>(sptr);
                                return SkTextBlob::MakeFromText(
                                        str, len, font, SkTextEncoding::kUTF8);
                            }),
                            allow_raw_pointers())
            .class_function("_MakeFromGlyphs",
                            optional_override([](WASMPointerU16 gPtr,
                                                 size_t byteLen,
                                                 const SkFont& font) -> sk_sp<SkTextBlob> {
                                const SkGlyphID* glyphs = reinterpret_cast<const SkGlyphID*>(gPtr);
                                return SkTextBlob::MakeFromText(
                                        glyphs, byteLen, font, SkTextEncoding::kGlyphID);
                            }),
                            allow_raw_pointers());

    class_<SkTypeface>("Typeface")
            .smart_ptr<sk_sp<SkTypeface>>("sk_sp<Typeface>")
            .class_function("GetDefault",
                            optional_override([]() -> sk_sp<SkTypeface> {
#if defined(CK_EMBED_FONT)
                                if (SK_EMBEDDED_FONTS.count == 0) {
                                    return nullptr;
                                }
                                static sk_sp<SkTypeface> default_face;
                                static SkOnce once;
                                once([] {
                                    const SkEmbeddedResource& fontEntry =
                                            SK_EMBEDDED_FONTS.entries[0];
                                    auto stream = std::make_unique<SkMemoryStream>(
                                            fontEntry.data, fontEntry.size, false);
                                    default_face = SkTypeface_FreeType::MakeFromStream(
                                            std::move(stream), SkFontArguments());
                                });
                                return default_face;
#else
                                return nullptr;
#endif
                            }),
                            allow_raw_pointers())
            .class_function(
                    "_MakeTypefaceFromData",
                    optional_override([](WASMPointerU8 fPtr, int flen) -> sk_sp<SkTypeface> {
                        uint8_t* font = reinterpret_cast<uint8_t*>(fPtr);
                        std::unique_ptr<SkMemoryStream> stream(new SkMemoryStream());
                        stream->setMemoryOwned(font, flen);
                        return SkTypeface_FreeType::MakeFromStream(std::move(stream),
                                                                   SkFontArguments());
                    }),
                    allow_raw_pointers())
            .function("getFamilyName", optional_override([](SkTypeface& self) -> JSString {
                          SkString s;
                          self.getFamilyName(&s);
                          return emscripten::val(s.c_str());
                      }))
            .function("_getGlyphIDs",
                      optional_override([](SkTypeface& self,
                                           WASMPointerU8 sptr,
                                           size_t strLen,
                                           size_t expectedCodePoints,
                                           WASMPointerU16 iPtr) -> int {
                          const char* str = reinterpret_cast<char*>(sptr);
                          SkSpan<SkGlyphID> glyphIDs = {reinterpret_cast<SkGlyphID*>(iPtr),
                                                        expectedCodePoints};

                          return self.textToGlyphs(str, strLen, SkTextEncoding::kUTF8, glyphIDs);
                      }));
#endif

    class_<SkVertices>("Vertices")
            .smart_ptr<sk_sp<SkVertices>>("sk_sp<Vertices>")
            .function("_bounds",
                      optional_override([](SkVertices& self, WASMPointerF32 fPtr) -> void {
                          SkRect* output = reinterpret_cast<SkRect*>(fPtr);
                          output[0] = self.bounds();
                      }))
            .function("uniqueID", &SkVertices::uniqueID);

    // Not intended to be called directly by clients
    class_<SkVertices::Builder>("_VerticesBuilder")
            .constructor<SkVertices::VertexMode, int, int, uint32_t>()
            .function("colors", optional_override([](SkVertices::Builder& self) -> WASMPointerF32 {
                          // Emscripten won't let us return bare pointers, but we can return ints
                          // just fine.
                          return reinterpret_cast<WASMPointerF32>(self.colors());
                      }))
            .function("detach", &SkVertices::Builder::detach)
            .function("indices", optional_override([](SkVertices::Builder& self) -> WASMPointerU16 {
                          // Emscripten won't let us return bare pointers, but we can return ints
                          // just fine.
                          return reinterpret_cast<WASMPointerU16>(self.indices());
                      }))
            .function("positions",
                      optional_override([](SkVertices::Builder& self) -> WASMPointerF32 {
                          // Emscripten won't let us return bare pointers, but we can return ints
                          // just fine.
                          return reinterpret_cast<WASMPointerF32>(self.positions());
                      }))
            .function("texCoords",
                      optional_override([](SkVertices::Builder& self) -> WASMPointerF32 {
                          // Emscripten won't let us return bare pointers, but we can return ints
                          // just fine.
                          return reinterpret_cast<WASMPointerF32>(self.texCoords());
                      }));

    enum_<SkAlphaType>("AlphaType")
            .value("Opaque", SkAlphaType::kOpaque_SkAlphaType)
            .value("Premul", SkAlphaType::kPremul_SkAlphaType)
            .value("Unpremul", SkAlphaType::kUnpremul_SkAlphaType);

    enum_<SkBlendMode>("BlendMode")
            .value("Clear", SkBlendMode::kClear)
            .value("Src", SkBlendMode::kSrc)
            .value("Dst", SkBlendMode::kDst)
            .value("SrcOver", SkBlendMode::kSrcOver)
            .value("DstOver", SkBlendMode::kDstOver)
            .value("SrcIn", SkBlendMode::kSrcIn)
            .value("DstIn", SkBlendMode::kDstIn)
            .value("SrcOut", SkBlendMode::kSrcOut)
            .value("DstOut", SkBlendMode::kDstOut)
            .value("SrcATop", SkBlendMode::kSrcATop)
            .value("DstATop", SkBlendMode::kDstATop)
            .value("Xor", SkBlendMode::kXor)
            .value("Plus", SkBlendMode::kPlus)
            .value("Modulate", SkBlendMode::kModulate)
            .value("Screen", SkBlendMode::kScreen)
            .value("Overlay", SkBlendMode::kOverlay)
            .value("Darken", SkBlendMode::kDarken)
            .value("Lighten", SkBlendMode::kLighten)
            .value("ColorDodge", SkBlendMode::kColorDodge)
            .value("ColorBurn", SkBlendMode::kColorBurn)
            .value("HardLight", SkBlendMode::kHardLight)
            .value("SoftLight", SkBlendMode::kSoftLight)
            .value("Difference", SkBlendMode::kDifference)
            .value("Exclusion", SkBlendMode::kExclusion)
            .value("Multiply", SkBlendMode::kMultiply)
            .value("Hue", SkBlendMode::kHue)
            .value("Saturation", SkBlendMode::kSaturation)
            .value("Color", SkBlendMode::kColor)
            .value("Luminosity", SkBlendMode::kLuminosity);

    enum_<SkBlurStyle>("BlurStyle")
            .value("Normal", SkBlurStyle::kNormal_SkBlurStyle)
            .value("Solid", SkBlurStyle::kSolid_SkBlurStyle)
            .value("Outer", SkBlurStyle::kOuter_SkBlurStyle)
            .value("Inner", SkBlurStyle::kInner_SkBlurStyle);

    enum_<SkClipOp>("ClipOp")
            .value("Difference", SkClipOp::kDifference)
            .value("Intersect", SkClipOp::kIntersect);

    enum_<SkColorChannel>("ColorChannel")
            .value("Red", SkColorChannel::kR)
            .value("Green", SkColorChannel::kG)
            .value("Blue", SkColorChannel::kB)
            .value("Alpha", SkColorChannel::kA);

    enum_<SkColorType>("ColorType")
            .value("Alpha_8", SkColorType::kAlpha_8_SkColorType)
            .value("RGB_565", SkColorType::kRGB_565_SkColorType)
            .value("RGBA_8888", SkColorType::kRGBA_8888_SkColorType)
            .value("BGRA_8888", SkColorType::kBGRA_8888_SkColorType)
            .value("RGBA_1010102", SkColorType::kRGBA_1010102_SkColorType)
            .value("RGB_101010x", SkColorType::kRGB_101010x_SkColorType)
            .value("Gray_8", SkColorType::kGray_8_SkColorType)
            .value("RGBA_F16", SkColorType::kRGBA_F16_SkColorType)
            .value("RGB_F16F16F16x", SkColorType::kRGB_F16F16F16x_SkColorType)
            .value("RGBA_F32", SkColorType::kRGBA_F32_SkColorType);

    enum_<SkPathFillType>("FillType")
            .value("Winding", SkPathFillType::kWinding)
            .value("EvenOdd", SkPathFillType::kEvenOdd);

    enum_<SkFilterMode>("FilterMode")
            .value("Nearest", SkFilterMode::kNearest)
            .value("Linear", SkFilterMode::kLinear);

    // Only used to control the encode function.
    // TODO(kjlubick): compile these out when the appropriate encoder is disabled.
    enum_<SkEncodedImageFormat>("ImageFormat")
            .value("PNG", SkEncodedImageFormat::kPNG)
            .value("JPEG", SkEncodedImageFormat::kJPEG)
            .value("WEBP", SkEncodedImageFormat::kWEBP);

    enum_<SkMipmapMode>("MipmapMode")
            .value("None", SkMipmapMode::kNone)
            .value("Nearest", SkMipmapMode::kNearest)
            .value("Linear", SkMipmapMode::kLinear);

    enum_<SkPaint::Style>("PaintStyle")
            .value("Fill", SkPaint::Style::kFill_Style)
            .value("Stroke", SkPaint::Style::kStroke_Style);

    enum_<SkPath1DPathEffect::Style>("Path1DEffect")
            .value("Translate", SkPath1DPathEffect::Style::kTranslate_Style)
            .value("Rotate", SkPath1DPathEffect::Style::kRotate_Style)
            .value("Morph", SkPath1DPathEffect::Style::kMorph_Style);

#ifdef CK_INCLUDE_PATHOPS
    enum_<SkPathOp>("PathOp")
            .value("Difference", SkPathOp::kDifference_SkPathOp)
            .value("Intersect", SkPathOp::kIntersect_SkPathOp)
            .value("Union", SkPathOp::kUnion_SkPathOp)
            .value("XOR", SkPathOp::kXOR_SkPathOp)
            .value("ReverseDifference", SkPathOp::kReverseDifference_SkPathOp);
#endif

    enum_<SkCanvas::PointMode>("PointMode")
            .value("Points", SkCanvas::PointMode::kPoints_PointMode)
            .value("Lines", SkCanvas::PointMode::kLines_PointMode)
            .value("Polygon", SkCanvas::PointMode::kPolygon_PointMode);

    enum_<SkPaint::Cap>("StrokeCap")
            .value("Butt", SkPaint::Cap::kButt_Cap)
            .value("Round", SkPaint::Cap::kRound_Cap)
            .value("Square", SkPaint::Cap::kSquare_Cap);

    enum_<SkPaint::Join>("StrokeJoin")
            .value("Miter", SkPaint::Join::kMiter_Join)
            .value("Round", SkPaint::Join::kRound_Join)
            .value("Bevel", SkPaint::Join::kBevel_Join);

#ifndef CK_NO_FONTS
    enum_<SkFontHinting>("FontHinting")
            .value("None", SkFontHinting::kNone)
            .value("Slight", SkFontHinting::kSlight)
            .value("Normal", SkFontHinting::kNormal)
            .value("Full", SkFontHinting::kFull);

    enum_<SkFont::Edging>("FontEdging")
#ifndef CK_NO_ALIAS_FONT
            .value("Alias", SkFont::Edging::kAlias)
#endif
            .value("AntiAlias", SkFont::Edging::kAntiAlias)
            .value("SubpixelAntiAlias", SkFont::Edging::kSubpixelAntiAlias);
#endif

    enum_<SkTileMode>("TileMode")
            .value("Clamp", SkTileMode::kClamp)
            .value("Repeat", SkTileMode::kRepeat)
            .value("Mirror", SkTileMode::kMirror)
            .value("Decal", SkTileMode::kDecal);

    enum_<SkVertices::VertexMode>("VertexMode")
            .value("Triangles", SkVertices::VertexMode::kTriangles_VertexMode)
            .value("TrianglesStrip", SkVertices::VertexMode::kTriangleStrip_VertexMode)
            .value("TriangleFan", SkVertices::VertexMode::kTriangleFan_VertexMode);

    // A value object is much simpler than a class - it is returned as a JS
    // object and does not require delete().
    // https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html#value-types

    value_object<SimpleImageInfo>("ImageInfo")
            .field("width", &SimpleImageInfo::width)
            .field("height", &SimpleImageInfo::height)
            .field("colorType", &SimpleImageInfo::colorType)
            .field("alphaType", &SimpleImageInfo::alphaType)
            .field("colorSpace", &SimpleImageInfo::colorSpace);

    value_object<StrokeOpts>("StrokeOpts")
            .field("width", &StrokeOpts::width)
            .field("miter_limit", &StrokeOpts::miter_limit)
            .field("join", &StrokeOpts::join)
            .field("cap", &StrokeOpts::cap)
            .field("precision", &StrokeOpts::precision);

    constant("MOVE_VERB", MOVE);
    constant("LINE_VERB", LINE);
    constant("QUAD_VERB", QUAD);
    constant("CONIC_VERB", CONIC);
    constant("CUBIC_VERB", CUBIC);
    constant("CLOSE_VERB", CLOSE);

    constant("SaveLayerInitWithPrevious",
             (int)SkCanvas::SaveLayerFlagsSet::kInitWithPrevious_SaveLayerFlag);
    constant("SaveLayerF16ColorType", (int)SkCanvas::SaveLayerFlagsSet::kF16ColorType);

    constant("ShadowTransparentOccluder", (int)SkShadowFlags::kTransparentOccluder_ShadowFlag);
    constant("ShadowGeometricOnly", (int)SkShadowFlags::kGeometricOnly_ShadowFlag);
    constant("ShadowDirectionalLight", (int)SkShadowFlags::kDirectionalLight_ShadowFlag);

#ifdef CK_INCLUDE_PARAGRAPH
    constant("_GlyphRunFlags_isWhiteSpace",
             (int)skia::textlayout::Paragraph::kWhiteSpace_VisitorFlag);
#endif
}
