// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/// Bindings for CanvasKit JavaScript API.
///
/// Prefer keeping the original CanvasKit names so it is easier to locate
/// the API behind these bindings in the Skia source code.
// ignore_for_file: non_constant_identifier_names
@JS()
library canvaskit_api;

import 'dart:async';
import 'dart:convert';
import 'dart:js_interop';
import 'dart:js_util' as js_util;
import 'dart:typed_data';

import 'package:meta/meta.dart';
import 'package:ui/ui.dart' as ui;

import '../browser_detection.dart';
import '../configuration.dart';
import '../dom.dart';
import 'renderer.dart';

/// Entrypoint into the CanvasKit API.
late CanvasKit canvasKit;

bool get _enableCanvasKitChromiumInAutoMode => browserSupportsCanvaskitChromium;

/// Sets the [CanvasKit] object on `window` so we can use `@JS()` to bind to
/// static APIs.
///
/// See, e.g. [SkPaint].
///
/// This also acts as a cache of an initialized CanvasKit instance. We can use
/// this, for example, to perform a hot restart without needing to redownload
/// and reinitialize CanvasKit.
@JS('window.flutterCanvasKit')
external set windowFlutterCanvasKit(CanvasKit? value);

@JS('window.flutterCanvasKit')
external CanvasKit? get windowFlutterCanvasKit;

@JS()
@anonymous
@staticInterop
class CanvasKit {}

extension CanvasKitExtension on CanvasKit {
  external SkBlendModeEnum get BlendMode;
  external SkPaintStyleEnum get PaintStyle;
  external SkStrokeCapEnum get StrokeCap;
  external SkStrokeJoinEnum get StrokeJoin;
  external SkBlurStyleEnum get BlurStyle;
  external SkTileModeEnum get TileMode;
  external SkFilterModeEnum get FilterMode;
  external SkMipmapModeEnum get MipmapMode;
  external SkFillTypeEnum get FillType;
  external SkAlphaTypeEnum get AlphaType;
  external SkColorTypeEnum get ColorType;
  external SkPathOpEnum get PathOp;
  external SkClipOpEnum get ClipOp;
  external SkPointModeEnum get PointMode;
  external SkVertexModeEnum get VertexMode;
  external SkRectHeightStyleEnum get RectHeightStyle;
  external SkRectWidthStyleEnum get RectWidthStyle;
  external SkAffinityEnum get Affinity;
  external SkTextAlignEnum get TextAlign;
  external SkTextHeightBehaviorEnum get TextHeightBehavior;
  external SkTextDirectionEnum get TextDirection;
  external SkFontWeightEnum get FontWeight;
  external SkFontSlantEnum get FontSlant;

  @JS('MakeAnimatedImageFromEncoded')
  external SkAnimatedImage? _MakeAnimatedImageFromEncoded(
      JSUint8Array imageData);
  SkAnimatedImage? MakeAnimatedImageFromEncoded(Uint8List imageData) =>
      _MakeAnimatedImageFromEncoded(imageData.toJS);

  external SkShaderNamespace get Shader;
  external SkMaskFilterNamespace get MaskFilter;
  external SkColorFilterNamespace get ColorFilter;
  external SkImageFilterNamespace get ImageFilter;
  external SkPathNamespace get Path;
  external SkTonalColors computeTonalColors(SkTonalColors inTonalColors);

  @JS('MakeVertices')
  external SkVertices _MakeVertices(
    SkVertexMode mode,
    JSFloat32Array positions,
    JSFloat32Array? textureCoordinates,
    JSUint32Array? colors,
    JSUint16Array? indices,
  );
  SkVertices MakeVertices(
    SkVertexMode mode,
    Float32List positions,
    Float32List? textureCoordinates,
    Uint32List? colors,
    Uint16List? indices,
  ) => _MakeVertices(mode, positions.toJS, textureCoordinates?.toJS,
      colors?.toJS, indices?.toJS);

  external SkParagraphBuilderNamespace get ParagraphBuilder;
  external SkParagraphStyle ParagraphStyle(
      SkParagraphStyleProperties properties);
  external SkTextStyle TextStyle(SkTextStyleProperties properties);
  external SkSurface MakeWebGLCanvasSurface(DomCanvasElement canvas);

  @JS('MakeSurface')
  external SkSurface _MakeSurface(
    JSNumber width,
    JSNumber height,
  );
  SkSurface MakeSurface(
    double width,
    double height,
  ) => _MakeSurface(width.toJS, height.toJS);

  @JS('getDataBytes')
  external JSUint8Array _getDataBytes(
    SkData skData,
  );
  Uint8List getDataBytes(
    SkData skData,
  ) => _getDataBytes(skData).toDart;

  // Text decoration enum is embedded in the CanvasKit object itself.
  @JS('NoDecoration')
  external JSNumber get _NoDecoration;
  double get NoDecoration => _NoDecoration.toDartDouble;

  @JS('UnderlineDecoration')
  external JSNumber get _UnderlineDecoration;
  double get UnderlineDecoration => _UnderlineDecoration.toDartDouble;

  @JS('OverlineDecoration')
  external JSNumber get _OverlineDecoration;
  double get OverlineDecoration => _OverlineDecoration.toDartDouble;

  @JS('LineThroughDecoration')
  external JSNumber get _LineThroughDecoration;
  double get LineThroughDecoration => _LineThroughDecoration.toDartDouble;
  // End of text decoration enum.

  external SkTextDecorationStyleEnum get DecorationStyle;
  external SkTextBaselineEnum get TextBaseline;
  external SkPlaceholderAlignmentEnum get PlaceholderAlignment;

  external SkFontMgrNamespace get FontMgr;
  external TypefaceFontProviderNamespace get TypefaceFontProvider;
  external FontCollectionNamespace get FontCollection;
  external SkTypefaceFactory get Typeface;

  @JS('GetWebGLContext')
  external JSNumber _GetWebGLContext(
      DomCanvasElement canvas, SkWebGLContextOptions options);
  double GetWebGLContext(
      DomCanvasElement canvas, SkWebGLContextOptions options) =>
        _GetWebGLContext(canvas, options).toDartDouble;

  @JS('MakeGrContext')
  external SkGrContext _MakeGrContext(JSNumber glContext);
  SkGrContext MakeGrContext(double glContext) =>
      _MakeGrContext(glContext.toJS);

  @JS('MakeOnScreenGLSurface')
  external SkSurface? _MakeOnScreenGLSurface(
    SkGrContext grContext,
    JSNumber width,
    JSNumber height,
    ColorSpace colorSpace,
    JSNumber sampleCount,
    JSNumber stencil,
  );
  SkSurface? MakeOnScreenGLSurface(
    SkGrContext grContext,
    double width,
    double height,
    ColorSpace colorSpace,
    int sampleCount,
    int stencil,
  ) => _MakeOnScreenGLSurface(grContext, width.toJS, height.toJS, colorSpace,
                              sampleCount.toJS, stencil.toJS);

  @JS('MakeRenderTarget')
  external SkSurface? _MakeRenderTarget(
    SkGrContext grContext,
    JSNumber width,
    JSNumber height,
  );
  SkSurface? MakeRenderTarget(
    SkGrContext grContext,
    int width,
    int height,
  ) => _MakeRenderTarget(grContext, width.toJS, height.toJS);

  external SkSurface MakeSWCanvasSurface(DomCanvasElement canvas);

  /// Creates an image from decoded pixels represented as a list of bytes.
  ///
  /// The pixel data must be encoded according to the image info in [info].
  ///
  /// Typically pixel data is obtained using [SkImage.readPixels]. The
  /// parameters specified in [SkImageInfo] passed [SkImage.readPixels] must
  /// match [info].
  @JS('MakeImage')
  external SkImage? _MakeImage(
    SkImageInfo info,
    JSUint8Array pixels,
    JSNumber bytesPerRow,
  );
  SkImage? MakeImage(
    SkImageInfo info,
    Uint8List pixels,
    double bytesPerRow,
  ) => _MakeImage(info, pixels.toJS, bytesPerRow.toJS);

  @JS('MakeLazyImageFromTextureSource')
  external SkImage? _MakeLazyImageFromTextureSource(
    JSAny src,
    SkPartialImageInfo info,
  );
  SkImage? MakeLazyImageFromTextureSource(
    Object src,
    SkPartialImageInfo info,
  ) => _MakeLazyImageFromTextureSource(src.toJSAnyShallow, info);
}

@JS('window.CanvasKitInit')
external JSAny _CanvasKitInit(CanvasKitInitOptions options);

Future<CanvasKit> CanvasKitInit(CanvasKitInitOptions options) {
  return js_util.promiseToFuture<CanvasKit>(
          _CanvasKitInit(options).toObjectShallow);
}

typedef LocateFileCallback = String Function(String file, String unusedBase);

JSFunction createLocateFileCallback(LocateFileCallback callback) =>
    callback.toJS;

@JS()
@anonymous
@staticInterop
class CanvasKitInitOptions {
  external factory CanvasKitInitOptions({
    required JSFunction locateFile,
  });
}

@JS('window.flutterCanvasKit.ColorSpace.SRGB')
external ColorSpace get SkColorSpaceSRGB;

@JS()
@staticInterop
class ColorSpace {}

@JS()
@anonymous
@staticInterop
class SkWebGLContextOptions {
  factory SkWebGLContextOptions({
    required double antialias,
    // WebGL version: 1 or 2.
    required double majorVersion,
  }) => SkWebGLContextOptions._(
    antialias: antialias.toJS, majorVersion: majorVersion.toJS
  );
  external factory SkWebGLContextOptions._({
    required JSNumber antialias,
    // WebGL version: 1 or 2.
    required JSNumber majorVersion,
  });
}

@JS('window.flutterCanvasKit.Surface')
@staticInterop
class SkSurface {}

extension SkSurfaceExtension on SkSurface {
  external SkCanvas getCanvas();
  external JSVoid flush();

  @JS('width')
  external JSNumber _width();
  double width() => _width().toDartDouble;

  @JS('height')
  external JSNumber _height();
  double height() => _height().toDartDouble;

  external JSVoid dispose();
  external SkImage makeImageSnapshot();
}

@JS()
@staticInterop
class SkGrContext {}

extension SkGrContextExtension on SkGrContext {
  @JS('setResourceCacheLimitBytes')
  external JSVoid _setResourceCacheLimitBytes(JSNumber limit);
  void setResourceCacheLimitBytes(double limit) =>
      _setResourceCacheLimitBytes(limit.toJS);

  external JSVoid releaseResourcesAndAbandonContext();
  external JSVoid delete();
}

@JS()
@anonymous
@staticInterop
class SkFontSlantEnum {}

extension SkFontSlantEnumExtension on SkFontSlantEnum {
  external SkFontSlant get Upright;
  external SkFontSlant get Italic;
}

@JS('window.flutterCanvasKit.FontSlant')
@staticInterop
class SkFontSlant {}

extension SkFontSlantExtension on SkFontSlant {
  @JS('value')
  external JSNumber get _value;
  double get value => _value.toDartDouble;
}

final List<SkFontSlant> _skFontSlants = <SkFontSlant>[
  canvasKit.FontSlant.Upright,
  canvasKit.FontSlant.Italic,
];

SkFontSlant toSkFontSlant(ui.FontStyle style) {
  return _skFontSlants[style.index];
}

@JS()
@anonymous
@staticInterop
class SkFontWeightEnum {}

extension SkFontWeightEnumExtension on SkFontWeightEnum {
  external SkFontWeight get Thin;
  external SkFontWeight get ExtraLight;
  external SkFontWeight get Light;
  external SkFontWeight get Normal;
  external SkFontWeight get Medium;
  external SkFontWeight get SemiBold;
  external SkFontWeight get Bold;
  external SkFontWeight get ExtraBold;
  external SkFontWeight get ExtraBlack;
}

@JS()
@staticInterop
class SkFontWeight {}

extension SkFontWeightExtension on SkFontWeight {
  @JS('value')
  external JSNumber get _value;
  double get value => _value.toDartDouble;
}

final List<SkFontWeight> _skFontWeights = <SkFontWeight>[
  canvasKit.FontWeight.Thin,
  canvasKit.FontWeight.ExtraLight,
  canvasKit.FontWeight.Light,
  canvasKit.FontWeight.Normal,
  canvasKit.FontWeight.Medium,
  canvasKit.FontWeight.SemiBold,
  canvasKit.FontWeight.Bold,
  canvasKit.FontWeight.ExtraBold,
  canvasKit.FontWeight.ExtraBlack,
];

SkFontWeight toSkFontWeight(ui.FontWeight weight) {
  return _skFontWeights[weight.index];
}

@JS()
@staticInterop
class SkAffinityEnum {}

extension SkAffinityEnumExtension on SkAffinityEnum {
  external SkAffinity get Upstream;
  external SkAffinity get Downstream;
}

@JS()
@staticInterop
class SkAffinity {}

extension SkAffinityExtension on SkAffinity {
  @JS('value')
  external JSNumber get _value;
  double get value => _value.toDartDouble;
}

final List<SkAffinity> _skAffinitys = <SkAffinity>[
  canvasKit.Affinity.Upstream,
  canvasKit.Affinity.Downstream,
];

SkAffinity toSkAffinity(ui.TextAffinity affinity) {
  return _skAffinitys[affinity.index];
}

@JS()
@staticInterop
class SkTextDirectionEnum {}

extension SkTextDirectionEnumExtension on SkTextDirectionEnum {
  external SkTextDirection get RTL;
  external SkTextDirection get LTR;
}

@JS()
@staticInterop
class SkTextDirection {}

extension SkTextDirectionExtension on SkTextDirection {
  @JS('value')
  external JSNumber get _value;
  double get value => _value.toDartDouble;
}

// Flutter enumerates text directions as RTL, LTR, while CanvasKit
// enumerates them LTR, RTL.
final List<SkTextDirection> _skTextDirections = <SkTextDirection>[
  canvasKit.TextDirection.RTL,
  canvasKit.TextDirection.LTR,
];

SkTextDirection toSkTextDirection(ui.TextDirection direction) {
  return _skTextDirections[direction.index];
}

@JS()
@staticInterop
class SkTextAlignEnum {}

extension SkTextAlignEnumExtension on SkTextAlignEnum {
  external SkTextAlign get Left;
  external SkTextAlign get Right;
  external SkTextAlign get Center;
  external SkTextAlign get Justify;
  external SkTextAlign get Start;
  external SkTextAlign get End;
}

@JS()
@staticInterop
class SkTextAlign {}

extension SkTextAlignExtension on SkTextAlign {
  @JS('value')
  external JSNumber get _value;
  double get value => _value.toDartDouble;
}

final List<SkTextAlign> _skTextAligns = <SkTextAlign>[
  canvasKit.TextAlign.Left,
  canvasKit.TextAlign.Right,
  canvasKit.TextAlign.Center,
  canvasKit.TextAlign.Justify,
  canvasKit.TextAlign.Start,
  canvasKit.TextAlign.End,
];

SkTextAlign toSkTextAlign(ui.TextAlign align) {
  return _skTextAligns[align.index];
}

@JS()
@staticInterop
class SkTextHeightBehaviorEnum {}

extension SkTextHeightBehaviorEnumExtension on SkTextHeightBehaviorEnum {
  external SkTextHeightBehavior get All;
  external SkTextHeightBehavior get DisableFirstAscent;
  external SkTextHeightBehavior get DisableLastDescent;
  external SkTextHeightBehavior get DisableAll;
}

@JS()
@staticInterop
class SkTextHeightBehavior {}

extension SkTextHeightBehaviorExtension on SkTextHeightBehavior {
  @JS('value')
  external JSNumber get _value;
  double get value => _value.toDartDouble;
}

final List<SkTextHeightBehavior> _skTextHeightBehaviors =
    <SkTextHeightBehavior>[
  canvasKit.TextHeightBehavior.All,
  canvasKit.TextHeightBehavior.DisableFirstAscent,
  canvasKit.TextHeightBehavior.DisableLastDescent,
  canvasKit.TextHeightBehavior.DisableAll,
];

SkTextHeightBehavior toSkTextHeightBehavior(ui.TextHeightBehavior behavior) {
  final int index = (behavior.applyHeightToFirstAscent ? 0 : 1 << 0) |
      (behavior.applyHeightToLastDescent ? 0 : 1 << 1);
  return _skTextHeightBehaviors[index];
}

@JS()
@staticInterop
class SkRectHeightStyleEnum {}

extension SkRectHeightStyleEnumExtension on SkRectHeightStyleEnum {
  external SkRectHeightStyle get Tight;
  external SkRectHeightStyle get Max;
  external SkRectHeightStyle get IncludeLineSpacingMiddle;
  external SkRectHeightStyle get IncludeLineSpacingTop;
  external SkRectHeightStyle get IncludeLineSpacingBottom;
  external SkRectHeightStyle get Strut;
}

@JS()
@staticInterop
class SkRectHeightStyle {}

extension SkRectHeightStyleExtension on SkRectHeightStyle {
  @JS('value')
  external JSNumber get _value;
  double get value => _value.toDartDouble;
}

final List<SkRectHeightStyle> _skRectHeightStyles = <SkRectHeightStyle>[
  canvasKit.RectHeightStyle.Tight,
  canvasKit.RectHeightStyle.Max,
  canvasKit.RectHeightStyle.IncludeLineSpacingMiddle,
  canvasKit.RectHeightStyle.IncludeLineSpacingTop,
  canvasKit.RectHeightStyle.IncludeLineSpacingBottom,
  canvasKit.RectHeightStyle.Strut,
];

SkRectHeightStyle toSkRectHeightStyle(ui.BoxHeightStyle style) {
  return _skRectHeightStyles[style.index];
}

@JS()
@staticInterop
class SkRectWidthStyleEnum {}

extension SkRectWidthStyleEnumExtension on SkRectWidthStyleEnum {
  external SkRectWidthStyle get Tight;
  external SkRectWidthStyle get Max;
}

@JS()
@staticInterop
class SkRectWidthStyle {}

extension SkRectWidthStyleExtension on SkRectWidthStyle {
  @JS('value')
  external JSNumber get _value;
  double get value => _value.toDartDouble;
}

final List<SkRectWidthStyle> _skRectWidthStyles = <SkRectWidthStyle>[
  canvasKit.RectWidthStyle.Tight,
  canvasKit.RectWidthStyle.Max,
];

SkRectWidthStyle toSkRectWidthStyle(ui.BoxWidthStyle style) {
  final int index = style.index;
  return _skRectWidthStyles[index < 2 ? index : 0];
}

@JS()
@staticInterop
class SkVertexModeEnum {}

extension SkVertexModeEnumExtension on SkVertexModeEnum {
  external SkVertexMode get Triangles;
  external SkVertexMode get TrianglesStrip;
  external SkVertexMode get TriangleFan;
}

@JS()
@staticInterop
class SkVertexMode {}

extension SkVertexModeExtension on SkVertexMode {
  @JS('value')
  external JSNumber get _value;
  double get value => _value.toDartDouble;
}

final List<SkVertexMode> _skVertexModes = <SkVertexMode>[
  canvasKit.VertexMode.Triangles,
  canvasKit.VertexMode.TrianglesStrip,
  canvasKit.VertexMode.TriangleFan,
];

SkVertexMode toSkVertexMode(ui.VertexMode mode) {
  return _skVertexModes[mode.index];
}

@JS()
@staticInterop
class SkPointModeEnum {}

extension SkPointModeEnumExtension on SkPointModeEnum {
  external SkPointMode get Points;
  external SkPointMode get Lines;
  external SkPointMode get Polygon;
}

@JS()
@staticInterop
class SkPointMode {}

extension SkPointModeExtension on SkPointMode {
  @JS('value')
  external JSNumber get _value;
  double get value => _value.toDartDouble;
}

final List<SkPointMode> _skPointModes = <SkPointMode>[
  canvasKit.PointMode.Points,
  canvasKit.PointMode.Lines,
  canvasKit.PointMode.Polygon,
];

SkPointMode toSkPointMode(ui.PointMode mode) {
  return _skPointModes[mode.index];
}

@JS()
@staticInterop
class SkClipOpEnum {}

extension SkClipOpEnumExtension on SkClipOpEnum {
  external SkClipOp get Difference;
  external SkClipOp get Intersect;
}

@JS()
@staticInterop
class SkClipOp {}

extension SkClipOpExtension on SkClipOp {
  @JS('value')
  external JSNumber get _value;
  double get value => _value.toDartDouble;
}

final List<SkClipOp> _skClipOps = <SkClipOp>[
  canvasKit.ClipOp.Difference,
  canvasKit.ClipOp.Intersect,
];

SkClipOp toSkClipOp(ui.ClipOp clipOp) {
  return _skClipOps[clipOp.index];
}

@JS()
@staticInterop
class SkFillTypeEnum {}

extension SkFillTypeEnumExtension on SkFillTypeEnum {
  external SkFillType get Winding;
  external SkFillType get EvenOdd;
}

@JS()
@staticInterop
class SkFillType {}

extension SkFillTypeExtension on SkFillType {
  @JS('value')
  external JSNumber get _value;
  double get value => _value.toDartDouble;
}

final List<SkFillType> _skFillTypes = <SkFillType>[
  canvasKit.FillType.Winding,
  canvasKit.FillType.EvenOdd,
];

SkFillType toSkFillType(ui.PathFillType fillType) {
  return _skFillTypes[fillType.index];
}

@JS()
@staticInterop
class SkPathOpEnum {}

extension SkPathOpEnumExtension on SkPathOpEnum {
  external SkPathOp get Difference;
  external SkPathOp get Intersect;
  external SkPathOp get Union;
  external SkPathOp get XOR;
  external SkPathOp get ReverseDifference;
}

@JS()
@staticInterop
class SkPathOp {}

extension SkPathOpExtension on SkPathOp {
  @JS('value')
  external JSNumber get _value;
  double get value => _value.toDartDouble;
}

final List<SkPathOp> _skPathOps = <SkPathOp>[
  canvasKit.PathOp.Difference,
  canvasKit.PathOp.Intersect,
  canvasKit.PathOp.Union,
  canvasKit.PathOp.XOR,
  canvasKit.PathOp.ReverseDifference,
];

SkPathOp toSkPathOp(ui.PathOperation pathOp) {
  return _skPathOps[pathOp.index];
}

@JS()
@staticInterop
class SkBlurStyleEnum {}

extension SkBlurStyleEnumExtension on SkBlurStyleEnum {
  external SkBlurStyle get Normal;
  external SkBlurStyle get Solid;
  external SkBlurStyle get Outer;
  external SkBlurStyle get Inner;
}

@JS()
@staticInterop
class SkBlurStyle {}

extension SkBlurStyleExtension on SkBlurStyle {
  @JS('value')
  external JSNumber get _value;
  double get value => _value.toDartDouble;
}

final List<SkBlurStyle> _skBlurStyles = <SkBlurStyle>[
  canvasKit.BlurStyle.Normal,
  canvasKit.BlurStyle.Solid,
  canvasKit.BlurStyle.Outer,
  canvasKit.BlurStyle.Inner,
];

SkBlurStyle toSkBlurStyle(ui.BlurStyle style) {
  return _skBlurStyles[style.index];
}

@JS()
@staticInterop
class SkStrokeCapEnum {}

extension SkStrokeCapEnumExtension on SkStrokeCapEnum {
  external SkStrokeCap get Butt;
  external SkStrokeCap get Round;
  external SkStrokeCap get Square;
}

@JS()
@staticInterop
class SkStrokeCap {}

extension SkStrokeCapExtension on SkStrokeCap {
  @JS('value')
  external JSNumber get _value;
  double get value => _value.toDartDouble;
}

final List<SkStrokeCap> _skStrokeCaps = <SkStrokeCap>[
  canvasKit.StrokeCap.Butt,
  canvasKit.StrokeCap.Round,
  canvasKit.StrokeCap.Square,
];

SkStrokeCap toSkStrokeCap(ui.StrokeCap strokeCap) {
  return _skStrokeCaps[strokeCap.index];
}

@JS()
@staticInterop
class SkPaintStyleEnum {}

extension SkPaintStyleEnumExtension on SkPaintStyleEnum {
  external SkPaintStyle get Stroke;
  external SkPaintStyle get Fill;
}

@JS()
@staticInterop
class SkPaintStyle {}

extension SkPaintStyleExtension on SkPaintStyle {
  @JS('value')
  external JSNumber get _value;
  double get value => _value.toDartDouble;
}

final List<SkPaintStyle> _skPaintStyles = <SkPaintStyle>[
  canvasKit.PaintStyle.Fill,
  canvasKit.PaintStyle.Stroke,
];

SkPaintStyle toSkPaintStyle(ui.PaintingStyle paintStyle) {
  return _skPaintStyles[paintStyle.index];
}

@JS()
@staticInterop
class SkBlendModeEnum {}

extension SkBlendModeEnumExtension on SkBlendModeEnum {
  external SkBlendMode get Clear;
  external SkBlendMode get Src;
  external SkBlendMode get Dst;
  external SkBlendMode get SrcOver;
  external SkBlendMode get DstOver;
  external SkBlendMode get SrcIn;
  external SkBlendMode get DstIn;
  external SkBlendMode get SrcOut;
  external SkBlendMode get DstOut;
  external SkBlendMode get SrcATop;
  external SkBlendMode get DstATop;
  external SkBlendMode get Xor;
  external SkBlendMode get Plus;
  external SkBlendMode get Modulate;
  external SkBlendMode get Screen;
  external SkBlendMode get Overlay;
  external SkBlendMode get Darken;
  external SkBlendMode get Lighten;
  external SkBlendMode get ColorDodge;
  external SkBlendMode get ColorBurn;
  external SkBlendMode get HardLight;
  external SkBlendMode get SoftLight;
  external SkBlendMode get Difference;
  external SkBlendMode get Exclusion;
  external SkBlendMode get Multiply;
  external SkBlendMode get Hue;
  external SkBlendMode get Saturation;
  external SkBlendMode get Color;
  external SkBlendMode get Luminosity;
}

@JS()
@staticInterop
class SkBlendMode {}

extension SkBlendModeExtension on SkBlendMode {
  @JS('value')
  external JSNumber get _value;
  double get value => _value.toDartDouble;
}

final List<SkBlendMode> _skBlendModes = <SkBlendMode>[
  canvasKit.BlendMode.Clear,
  canvasKit.BlendMode.Src,
  canvasKit.BlendMode.Dst,
  canvasKit.BlendMode.SrcOver,
  canvasKit.BlendMode.DstOver,
  canvasKit.BlendMode.SrcIn,
  canvasKit.BlendMode.DstIn,
  canvasKit.BlendMode.SrcOut,
  canvasKit.BlendMode.DstOut,
  canvasKit.BlendMode.SrcATop,
  canvasKit.BlendMode.DstATop,
  canvasKit.BlendMode.Xor,
  canvasKit.BlendMode.Plus,
  canvasKit.BlendMode.Modulate,
  canvasKit.BlendMode.Screen,
  canvasKit.BlendMode.Overlay,
  canvasKit.BlendMode.Darken,
  canvasKit.BlendMode.Lighten,
  canvasKit.BlendMode.ColorDodge,
  canvasKit.BlendMode.ColorBurn,
  canvasKit.BlendMode.HardLight,
  canvasKit.BlendMode.SoftLight,
  canvasKit.BlendMode.Difference,
  canvasKit.BlendMode.Exclusion,
  canvasKit.BlendMode.Multiply,
  canvasKit.BlendMode.Hue,
  canvasKit.BlendMode.Saturation,
  canvasKit.BlendMode.Color,
  canvasKit.BlendMode.Luminosity,
];

SkBlendMode toSkBlendMode(ui.BlendMode blendMode) {
  return _skBlendModes[blendMode.index];
}

@JS()
@staticInterop
class SkStrokeJoinEnum {}

extension SkStrokeJoinEnumExtension on SkStrokeJoinEnum {
  external SkStrokeJoin get Miter;
  external SkStrokeJoin get Round;
  external SkStrokeJoin get Bevel;
}

@JS()
@staticInterop
class SkStrokeJoin {}

extension SkStrokeJoinExtension on SkStrokeJoin {
  @JS('value')
  external JSNumber get _value;
  double get value => _value.toDartDouble;
}

final List<SkStrokeJoin> _skStrokeJoins = <SkStrokeJoin>[
  canvasKit.StrokeJoin.Miter,
  canvasKit.StrokeJoin.Round,
  canvasKit.StrokeJoin.Bevel,
];

SkStrokeJoin toSkStrokeJoin(ui.StrokeJoin strokeJoin) {
  return _skStrokeJoins[strokeJoin.index];
}

@JS()
@staticInterop
class SkTileModeEnum {}

extension SkTileModeEnumExtension on SkTileModeEnum {
  external SkTileMode get Clamp;
  external SkTileMode get Repeat;
  external SkTileMode get Mirror;
  external SkTileMode get Decal;
}

@JS()
@staticInterop
class SkTileMode {}

extension SkTileModeExtension on SkTileMode {
  @JS('value')
  external JSNumber get _value;
  double get value => _value.toDartDouble;
}

final List<SkTileMode> _skTileModes = <SkTileMode>[
  canvasKit.TileMode.Clamp,
  canvasKit.TileMode.Repeat,
  canvasKit.TileMode.Mirror,
  canvasKit.TileMode.Decal,
];

SkTileMode toSkTileMode(ui.TileMode mode) {
  return _skTileModes[mode.index];
}

@JS()
@staticInterop
class SkFilterModeEnum {}

extension SkFilterModeEnumExtension on SkFilterModeEnum {
  external SkFilterMode get Nearest;
  external SkFilterMode get Linear;
}

@JS()
@staticInterop
class SkFilterMode {}

extension SkFilterModeExtension on SkFilterMode {
  @JS('value')
  external JSNumber get _value;
  double get value => _value.toDartDouble;
}

SkFilterMode toSkFilterMode(ui.FilterQuality filterQuality) {
  return filterQuality == ui.FilterQuality.none
      ? canvasKit.FilterMode.Nearest
      : canvasKit.FilterMode.Linear;
}

@JS()
@staticInterop
class SkMipmapModeEnum {}

extension SkMipmapModeEnumExtension on SkMipmapModeEnum {
  external SkMipmapMode get None;
  external SkMipmapMode get Nearest;
  external SkMipmapMode get Linear;
}

@JS()
@staticInterop
class SkMipmapMode {}

extension SkMipmapModeExtension on SkMipmapMode {
  @JS('value')
  external JSNumber get _value;
  double get value => _value.toDartDouble;
}

SkMipmapMode toSkMipmapMode(ui.FilterQuality filterQuality) {
  return filterQuality == ui.FilterQuality.medium
      ? canvasKit.MipmapMode.Linear
      : canvasKit.MipmapMode.None;
}

@JS()
@staticInterop
class SkAlphaTypeEnum {}

extension SkAlphaTypeEnumExtension on SkAlphaTypeEnum {
  external SkAlphaType get Opaque;
  external SkAlphaType get Premul;
  external SkAlphaType get Unpremul;
}

@JS()
@staticInterop
class SkAlphaType {}

extension SkAlphaTypeExtension on SkAlphaType {
  @JS('value')
  external JSNumber get _value;
  double get value => _value.toDartDouble;
}

@JS()
@staticInterop
class SkColorTypeEnum {}

extension SkColorTypeEnumExtension on SkColorTypeEnum {
  external SkColorType get Alpha_8;
  external SkColorType get RGB_565;
  external SkColorType get ARGB_4444;
  external SkColorType get RGBA_8888;
  external SkColorType get RGB_888x;
  external SkColorType get BGRA_8888;
  external SkColorType get RGBA_1010102;
  external SkColorType get RGB_101010x;
  external SkColorType get Gray_8;
  external SkColorType get RGBA_F16;
  external SkColorType get RGBA_F32;
}

@JS()
@staticInterop
class SkColorType {}

extension SkColorTypeExtension on SkColorType {
  @JS('value')
  external JSNumber get _value;
  double get value => _value.toDartDouble;
}

@JS()
@anonymous
@staticInterop
class SkAnimatedImage {}

extension SkAnimatedImageExtension on SkAnimatedImage {
  @JS('getFrameCount')
  external JSNumber _getFrameCount();
  double getFrameCount() => _getFrameCount().toDartDouble;

  @JS('getRepetitionCount')
  external JSNumber _getRepetitionCount();
  double getRepetitionCount() => _getRepetitionCount().toDartDouble;

  /// Returns duration in milliseconds.
  @JS('currentFrameDuration')
  external JSNumber _currentFrameDuration();
  double currentFrameDuration() => _currentFrameDuration().toDartDouble;

  /// Advances to the next frame and returns its duration in milliseconds.
  @JS('decodeNextFrame')
  external JSNumber _decodeNextFrame();
  double decodeNextFrame() => _decodeNextFrame().toDartDouble;

  external SkImage makeImageAtCurrentFrame();

  @JS('width')
  external JSNumber _width();
  double width() => _width().toDartDouble;

  @JS('height')
  external JSNumber _height();
  double height() => _height().toDartDouble;

  /// Deletes the C++ object.
  ///
  /// This object is no longer usable after calling this method.
  external JSVoid delete();

  @JS('isDeleted')
  external JSBoolean _isDeleted();
  bool isDeleted() => _isDeleted().toDart;
}

@JS()
@anonymous
@staticInterop
class SkImage {}

extension SkImageExtension on SkImage {
  external JSVoid delete();

  @JS('width')
  external JSNumber _width();
  double width() => _width().toDartDouble;

  @JS('height')
  external JSNumber _height();
  double height() => _height().toDartDouble;

  @JS('makeShaderCubic')
  external SkShader _makeShaderCubic(
    SkTileMode tileModeX,
    SkTileMode tileModeY,
    JSNumber B,
    JSNumber C,
    JSFloat32Array? matrix, // 3x3 matrix
  );
  SkShader makeShaderCubic(
    SkTileMode tileModeX,
    SkTileMode tileModeY,
    double B,
    double C,
    Float32List? matrix, // 3x3 matrix
  ) => _makeShaderCubic(tileModeX, tileModeY, B.toJS, C.toJS, matrix?.toJS);

  @JS('makeShaderOptions')
  external SkShader _makeShaderOptions(
    SkTileMode tileModeX,
    SkTileMode tileModeY,
    SkFilterMode filterMode,
    SkMipmapMode mipmapMode,
    JSFloat32Array? matrix, // 3x3 matrix
  );
  SkShader makeShaderOptions(
    SkTileMode tileModeX,
    SkTileMode tileModeY,
    SkFilterMode filterMode,
    SkMipmapMode mipmapMode,
    Float32List? matrix, // 3x3 matrix
  ) => _makeShaderOptions(tileModeX, tileModeY, filterMode, mipmapMode,
                          matrix?.toJS);

  @JS('readPixels')
  external JSUint8Array _readPixels(
      JSNumber srcX, JSNumber srcY, SkImageInfo imageInfo);
  Uint8List readPixels(double srcX, double srcY, SkImageInfo imageInfo) =>
      _readPixels(srcX.toJS, srcY.toJS, imageInfo).toDart;

  @JS('encodeToBytes')
  external JSUint8Array? _encodeToBytes();
  Uint8List? encodeToBytes() => _encodeToBytes()?.toDart;

  @JS('isAliasOf')
  external JSBoolean _isAliasOf(SkImage other);
  bool isAliasOf(SkImage other) => _isAliasOf(other).toDart;

  @JS('isDeleted')
  external JSBoolean _isDeleted();
  bool isDeleted() => _isDeleted().toDart;
}

@JS()
@staticInterop
class SkShaderNamespace {}

extension SkShaderNamespaceExtension on SkShaderNamespace {
  @JS('MakeLinearGradient')
  external SkShader _MakeLinearGradient(
    JSFloat32Array from, // 2-element array
    JSFloat32Array to, // 2-element array
    JSUint32Array colors,
    JSFloat32Array colorStops,
    SkTileMode tileMode,
    JSFloat32Array? matrix,
  );
  SkShader MakeLinearGradient(
    Float32List from, // 2-element array
    Float32List to, // 2-element array
    Uint32List colors,
    Float32List colorStops,
    SkTileMode tileMode,
    Float32List? matrix,
  ) => _MakeLinearGradient(from.toJS, to.toJS, colors.toJS, colorStops.toJS,
                           tileMode, matrix?.toJS);

  @JS('MakeRadialGradient')
  external SkShader _MakeRadialGradient(
    JSFloat32Array center, // 2-element array
    JSNumber radius,
    JSUint32Array colors,
    JSFloat32Array colorStops,
    SkTileMode tileMode,
    JSFloat32Array? matrix, // 3x3 matrix
    JSNumber flags,
  );
  SkShader MakeRadialGradient(
    Float32List center, // 2-element array
    double radius,
    Uint32List colors,
    Float32List colorStops,
    SkTileMode tileMode,
    Float32List? matrix, // 3x3 matrix
    double flags,
  ) => _MakeRadialGradient(center.toJS, radius.toJS, colors.toJS,
                           colorStops.toJS, tileMode, matrix?.toJS,
                           flags.toJS);

  @JS('MakeTwoPointConicalGradient')
  external SkShader _MakeTwoPointConicalGradient(
    JSFloat32Array focal,
    JSNumber focalRadius,
    JSFloat32Array center,
    JSNumber radius,
    JSUint32Array colors,
    JSFloat32Array colorStops,
    SkTileMode tileMode,
    JSFloat32Array? matrix, // 3x3 matrix
    JSNumber flags,
  );
  SkShader MakeTwoPointConicalGradient(
    Float32List focal,
    double focalRadius,
    Float32List center,
    double radius,
    Uint32List colors,
    Float32List colorStops,
    SkTileMode tileMode,
    Float32List? matrix, // 3x3 matrix
    double flags,
  ) => _MakeTwoPointConicalGradient(focal.toJS, focalRadius.toJS, center.toJS,
                                    radius.toJS, colors.toJS, colorStops.toJS,
                                    tileMode, matrix?.toJS, flags.toJS);

  @JS('MakeSweepGradient')
  external SkShader _MakeSweepGradient(
    JSNumber cx,
    JSNumber cy,
    JSUint32Array colors,
    JSFloat32Array colorStops,
    SkTileMode tileMode,
    JSFloat32Array? matrix, // 3x3 matrix
    JSNumber flags,
    JSNumber startAngle,
    JSNumber endAngle,
  );
  SkShader MakeSweepGradient(
    double cx,
    double cy,
    Uint32List colors,
    Float32List colorStops,
    SkTileMode tileMode,
    Float32List? matrix, // 3x3 matrix
    double flags,
    double startAngle,
    double endAngle,
  ) => _MakeSweepGradient(cx.toJS, cy.toJS, colors.toJS, colorStops.toJS,
                          tileMode, matrix?.toJS, flags.toJS, startAngle.toJS,
                          endAngle.toJS);
}

@JS()
@anonymous
@staticInterop
class SkShader {}

extension SkShaderExtension on SkShader {
  external JSVoid delete();
}

@JS()
@staticInterop
class SkMaskFilterNamespace {}

extension SkMaskFilterNamespaceExtension on SkMaskFilterNamespace {
  // Creates a blur MaskFilter.
  //
  // Returns `null` if [sigma] is 0 or infinite.
  @JS('MakeBlur')
  external SkMaskFilter? _MakeBlur(
      SkBlurStyle blurStyle, JSNumber sigma, JSBoolean respectCTM);
  SkMaskFilter? MakeBlur(
      SkBlurStyle blurStyle, double sigma, bool respectCTM) =>
      _MakeBlur(blurStyle, sigma.toJS, respectCTM.toJS);
}

// This needs to be bound to top-level because SkPaint is initialized
// with `new`. Also in Dart you can't write this:
//
//     external SkPaint SkPaint();
@JS('window.flutterCanvasKit.Paint')
@staticInterop
class SkPaint {
  external factory SkPaint();
}

extension SkPaintExtension on SkPaint {
  external JSVoid setBlendMode(SkBlendMode blendMode);
  external JSVoid setStyle(SkPaintStyle paintStyle);

  @JS('setStrokeWidth')
  external JSVoid _setStrokeWidth(JSNumber width);
  JSVoid setStrokeWidth(double width) => _setStrokeWidth(width.toJS);

  external JSVoid setStrokeCap(SkStrokeCap cap);
  external JSVoid setStrokeJoin(SkStrokeJoin join);

  @JS('setAntiAlias')
  external JSVoid _setAntiAlias(JSBoolean isAntiAlias);
  void setAntiAlias(bool isAntiAlias) => _setAntiAlias(isAntiAlias.toJS);

  @JS('setColorInt')
  external JSVoid _setColorInt(JSNumber color);
  void setColorInt(double color) => _setColorInt(color.toJS);

  external JSVoid setShader(SkShader? shader);
  external JSVoid setMaskFilter(SkMaskFilter? maskFilter);
  external JSVoid setColorFilter(SkColorFilter? colorFilter);

  @JS('setStrokeMiter')
  external JSVoid _setStrokeMiter(JSNumber miterLimit);
  void setStrokeMiter(double miterLimit) => _setStrokeMiter(miterLimit.toJS);

  external JSVoid setImageFilter(SkImageFilter? imageFilter);
  external JSVoid delete();
}

@JS()
@anonymous
@staticInterop
abstract class CkFilterOptions {}

@JS()
@anonymous
@staticInterop
class _CkCubicFilterOptions extends CkFilterOptions {
  external factory _CkCubicFilterOptions(
      {required JSNumber B, required JSNumber C});
}

@JS()
@anonymous
@staticInterop
class _CkTransformFilterOptions extends CkFilterOptions {
  external factory _CkTransformFilterOptions(
      {SkFilterMode filter, SkMipmapMode mipmap});
}

final Map<ui.FilterQuality, CkFilterOptions> _filterOptions =
    <ui.FilterQuality, CkFilterOptions>{
  ui.FilterQuality.none: _CkTransformFilterOptions(
    filter: canvasKit.FilterMode.Nearest,
    mipmap: canvasKit.MipmapMode.None,
  ),
  ui.FilterQuality.low: _CkTransformFilterOptions(
    filter: canvasKit.FilterMode.Linear,
    mipmap: canvasKit.MipmapMode.None,
  ),
  ui.FilterQuality.medium: _CkTransformFilterOptions(
    filter: canvasKit.FilterMode.Linear,
    mipmap: canvasKit.MipmapMode.Linear,
  ),
  ui.FilterQuality.high: _CkCubicFilterOptions(
    B: (1.0 / 3).toJS,
    C: (1.0 / 3).toJS,
  ),
};

CkFilterOptions toSkFilterOptions(ui.FilterQuality filterQuality) {
  return _filterOptions[filterQuality]!;
}

@JS()
@anonymous
@staticInterop
class SkMaskFilter {}

extension SkMaskFilterExtension on SkMaskFilter {
  external JSVoid delete();
}

@JS()
@staticInterop
class SkColorFilterNamespace {}

extension SkColorFilterNamespaceExtension on SkColorFilterNamespace {
  @JS('MakeBlend')
  external SkColorFilter? _MakeBlend(
      JSFloat32Array color, SkBlendMode blendMode);
  SkColorFilter? MakeBlend(Float32List color, SkBlendMode blendMode) =>
      _MakeBlend(color.toJS, blendMode);

  @JS('MakeMatrix')
  external SkColorFilter _MakeMatrix(
    JSFloat32Array matrix, // 20-element matrix
  );
  SkColorFilter MakeMatrix(
    Float32List matrix, // 20-element matrix
  ) => _MakeMatrix(matrix.toJS);

  external SkColorFilter MakeLinearToSRGBGamma();
  external SkColorFilter MakeSRGBToLinearGamma();
  external SkColorFilter MakeCompose(SkColorFilter? outer, SkColorFilter inner);
}

@JS()
@anonymous
@staticInterop
class SkColorFilter {}

extension SkColorFilterExtension on SkColorFilter {
  external JSVoid delete();
}

@JS()
@staticInterop
class SkImageFilterNamespace {}

extension SkImageFilterNamespaceExtension on SkImageFilterNamespace {
  @JS('MakeBlur')
  external SkImageFilter _MakeBlur(
    JSNumber sigmaX,
    JSNumber sigmaY,
    SkTileMode tileMode,
    JSVoid input, // we don't use this yet
  );
  SkImageFilter MakeBlur(
    double sigmaX,
    double sigmaY,
    SkTileMode tileMode,
    void input, // we don't use this yet
  ) => _MakeBlur(sigmaX.toJS, sigmaY.toJS, tileMode, input);

  @JS('MakeMatrixTransform')
  external SkImageFilter _MakeMatrixTransform(
    JSFloat32Array matrix, // 3x3 matrix
    CkFilterOptions filterOptions,
    JSVoid input, // we don't use this yet
  );
  SkImageFilter MakeMatrixTransform(
    Float32List matrix, // 3x3 matrix
    CkFilterOptions filterOptions,
    void input, // we don't use this yet
  ) => _MakeMatrixTransform(matrix.toJS, filterOptions, input);

  external SkImageFilter MakeColorFilter(
    SkColorFilter colorFilter,
    JSVoid input, // we don't use this yet
  );

  external SkImageFilter MakeCompose(
    SkImageFilter outer,
    SkImageFilter inner,
  );
}

@JS()
@anonymous
@staticInterop
class SkImageFilter {}

extension SkImageFilterExtension on SkImageFilter {
  external JSVoid delete();


  @JS('getOutputBounds')
  external JSInt32Array _getOutputBounds(JSFloat32Array bounds);
  Int32List getOutputBounds(Float32List bounds) =>
      _getOutputBounds(bounds.toJS).toDart;
}

@JS()
@staticInterop
class SkPathNamespace {}

extension SkPathNamespaceExtension on SkPathNamespace {
  /// Creates an [SkPath] using commands obtained from [SkPath.toCmds].
  @JS('MakeFromCmds')
  external SkPath _MakeFromCmds(JSAny pathCommands);
  SkPath MakeFromCmds(List<dynamic> pathCommands) =>
      _MakeFromCmds(pathCommands.toJSAnyShallow);

  /// Creates an [SkPath] by combining [path1] and [path2] using [pathOp].
  external SkPath MakeFromOp(SkPath path1, SkPath path2, SkPathOp pathOp);
}

/// Converts a 4x4 Flutter matrix (represented as a [Float32List] in
/// column major order) to an SkM44 which is a 4x4 matrix represented
/// as a [Float32List] in row major order.
Float32List toSkM44FromFloat32(Float32List matrix4) {
  final Float32List skM44 = Float32List(16);
  for (int r = 0; r < 4; r++) {
    for (int c = 0; c < 4; c++) {
      skM44[c * 4 + r] = matrix4[r * 4 + c];
    }
  }
  return skM44;
}

// Mappings from SkMatrix-index to input-index.
const List<int> _skMatrixIndexToMatrix4Index = <int>[
  0, 4, 12, // Row 1
  1, 5, 13, // Row 2
  3, 7, 15, // Row 3
];

/// Converts a 4x4 Flutter matrix (represented as a [Float32List]) to an
/// SkMatrix, which is a 3x3 transform matrix.
Float32List toSkMatrixFromFloat32(Float32List matrix4) {
  final Float32List skMatrix = Float32List(9);
  for (int i = 0; i < 9; ++i) {
    final int matrix4Index = _skMatrixIndexToMatrix4Index[i];
    if (matrix4Index < matrix4.length) {
      skMatrix[i] = matrix4[matrix4Index];
    } else {
      skMatrix[i] = 0.0;
    }
  }
  return skMatrix;
}

/// Converts a 4x4 Flutter matrix (represented as a [Float32List]) to an
/// SkMatrix, which is a 3x3 transform matrix.
Float32List toSkMatrixFromFloat64(Float64List matrix4) {
  final Float32List skMatrix = Float32List(9);
  for (int i = 0; i < 9; ++i) {
    final int matrix4Index = _skMatrixIndexToMatrix4Index[i];
    if (matrix4Index < matrix4.length) {
      skMatrix[i] = matrix4[matrix4Index];
    } else {
      skMatrix[i] = 0.0;
    }
  }
  return skMatrix;
}

/// Converts an [offset] into an `[x, y]` pair stored in a `Float32List`.
///
/// The returned list can be passed to CanvasKit API that take points.
Float32List toSkPoint(ui.Offset offset) {
  final Float32List point = Float32List(2);
  point[0] = offset.dx;
  point[1] = offset.dy;
  return point;
}

/// Color stops used when the framework specifies `null`.
final Float32List _kDefaultSkColorStops = Float32List(2)
  ..[0] = 0
  ..[1] = 1;

/// Converts a list of color stops into a Skia-compatible JS array or color stops.
///
/// In Flutter `null` means two color stops `[0, 1]` that in Skia must be specified explicitly.
Float32List toSkColorStops(List<double>? colorStops) {
  if (colorStops == null) {
    return _kDefaultSkColorStops;
  }

  final int len = colorStops.length;
  final Float32List skColorStops = Float32List(len);
  for (int i = 0; i < len; i++) {
    skColorStops[i] = colorStops[i];
  }
  return skColorStops;
}

@JS()
@staticInterop
abstract class _NativeType {}

@JS('Float32Array')
external _NativeType get _nativeFloat32ArrayType;

@JS('Uint32Array')
external _NativeType get _nativeUint32ArrayType;

@JS('window.flutterCanvasKit.Malloc')
external JSAny _malloc(_NativeType nativeType, JSNumber length);

/// Allocates a [Float32List] of [length] elements, backed by WASM memory,
/// managed by a [SkFloat32List].
///
/// To free the allocated array use [free].
SkFloat32List mallocFloat32List(int length) {
  return _malloc(_nativeFloat32ArrayType, length.toJS) as SkFloat32List;
}

/// Allocates a [Uint32List] of [length] elements, backed by WASM memory,
/// managed by a [SkUint32List].
///
/// To free the allocated array use [free].
SkUint32List mallocUint32List(int length) {
  return _malloc(_nativeUint32ArrayType, length.toJS) as SkUint32List;
}

/// Frees the WASM memory occupied by a [SkFloat32List] or [SkUint32List].
///
/// The [list] is no longer usable after calling this function.
///
/// Use this function to free lists owned by the engine.
@JS('window.flutterCanvasKit.Free')
external JSVoid free(MallocObj list);

@JS()
@staticInterop
abstract class MallocObj {}

/// Wraps a [Float32List] backed by WASM memory.
///
/// This wrapper is necessary because the raw [Float32List] will get detached
/// when WASM grows its memory. Call [toTypedArray] to get a new instance
/// that's attached to the current WASM memory block.
@JS()
@staticInterop
class SkFloat32List extends MallocObj {}

extension SkFloat32ListExtension on SkFloat32List {
  /// The number of objects this pointer refers to.
  @JS('length')
  external JSNumber get _length;
  double get length => _length.toDartDouble;

  @JS('length')
  external set _length(JSNumber length);
  set length(double l) => _length = l.toJS;

  /// Returns the [Float32List] object backed by WASM memory.
  ///
  /// Do not reuse the returned array across multiple WASM function/method
  /// invocations that may lead to WASM memory to grow. When WASM memory
  /// grows, the returned [Float32List] object becomes "detached" and is no
  /// longer usable. Instead, call this method every time you need to read from
  /// or write to the list.
  @JS('toTypedArray')
  external JSFloat32Array _toTypedArray();
  Float32List toTypedArray() => _toTypedArray().toDart;
}

/// Wraps a [Uint32List] backed by WASM memory.
///
/// This wrapper is necessary because the raw [Uint32List] will get detached
/// when WASM grows its memory. Call [toTypedArray] to get a new instance
/// that's attached to the current WASM memory block.
@JS()
@staticInterop
class SkUint32List extends MallocObj {}

extension SkUint32ListExtension on SkUint32List {
  /// The number of objects this pointer refers to.
  @JS('length')
  external JSNumber get _length;
  double get length => _length.toDartDouble;

  @JS('length')
  external set _length(JSNumber length);
  set length(double l) => _length = l.toJS;

  /// Returns the [Uint32List] object backed by WASM memory.
  ///
  /// Do not reuse the returned array across multiple WASM function/method
  /// invocations that may lead to WASM memory to grow. When WASM memory
  /// grows, the returned [Uint32List] object becomes "detached" and is no
  /// longer usable. Instead, call this method every time you need to read from
  /// or write to the list.
  @JS('toTypedArray')
  external JSUint32Array _toTypedArray();
  Uint32List toTypedArray() => _toTypedArray().toDart;
}

/// Writes [color] information into the given [skColor] buffer.
Float32List _populateSkColor(SkFloat32List skColor, ui.Color color) {
  final Float32List array = skColor.toTypedArray();
  array[0] = color.red / 255.0;
  array[1] = color.green / 255.0;
  array[2] = color.blue / 255.0;
  array[3] = color.alpha / 255.0;
  return array;
}

/// Unpacks the [color] into CanvasKit-compatible representation stored
/// in a shared memory location #1.
///
/// Use this only for passing transient data to CanvasKit. Because the
/// memory is shared the value will not persist.
Float32List toSharedSkColor1(ui.Color color) {
  return _populateSkColor(_sharedSkColor1, color);
}

final SkFloat32List _sharedSkColor1 = mallocFloat32List(4);

/// Unpacks the [color] into CanvasKit-compatible representation stored
/// in a shared memory location #2.
///
/// Use this only for passing transient data to CanvasKit. Because the
/// memory is shared the value will not persist.
Float32List toSharedSkColor2(ui.Color color) {
  return _populateSkColor(_sharedSkColor2, color);
}

final SkFloat32List _sharedSkColor2 = mallocFloat32List(4);

/// Unpacks the [color] into CanvasKit-compatible representation stored
/// in a shared memory location #3.
///
/// Use this only for passing transient data to CanvasKit. Because the
/// memory is shared the value will not persist.
Float32List toSharedSkColor3(ui.Color color) {
  return _populateSkColor(_sharedSkColor3, color);
}

final SkFloat32List _sharedSkColor3 = mallocFloat32List(4);

@JS('window.flutterCanvasKit.Path')
@staticInterop
class SkPath {
  external factory SkPath();
  external factory SkPath.from(SkPath other);
}

extension SkPathExtension on SkPath {
  external JSVoid setFillType(SkFillType fillType);

  @JS('addArc')
  external JSVoid _addArc(
    JSFloat32Array oval,
    JSNumber startAngleDegrees,
    JSNumber sweepAngleDegrees,
  );
  void addArc(
    Float32List oval,
    double startAngleDegrees,
    double sweepAngleDegrees,
  ) => _addArc(oval.toJS, startAngleDegrees.toJS, sweepAngleDegrees.toJS);

  @JS('addOval')
  external JSVoid _addOval(
    JSFloat32Array oval,
    JSBoolean counterClockWise,
    JSNumber startIndex,
  );
  void addOval(
    Float32List oval,
    bool counterClockWise,
    double startIndex,
  ) => _addOval(oval.toJS, counterClockWise.toJS, startIndex.toJS);

  @JS('addPath')
  external JSVoid _addPath(
    SkPath other,
    JSNumber scaleX,
    JSNumber skewX,
    JSNumber transX,
    JSNumber skewY,
    JSNumber scaleY,
    JSNumber transY,
    JSNumber pers0,
    JSNumber pers1,
    JSNumber pers2,
    JSBoolean extendPath,
  );
  void addPath(
    SkPath other,
    double scaleX,
    double skewX,
    double transX,
    double skewY,
    double scaleY,
    double transY,
    double pers0,
    double pers1,
    double pers2,
    bool extendPath,
  ) => _addPath(other, scaleX.toJS, skewX.toJS, transX.toJS, skewY.toJS,
                scaleY.toJS, transY.toJS, pers0.toJS, pers1.toJS, pers2.toJS,
                extendPath.toJS);

  @JS('addPoly')
  external JSVoid _addPoly(
    JSFloat32Array points,
    JSBoolean close,
  );
  void addPoly(
    Float32List points,
    bool close,
  ) => _addPoly(points.toJS, close.toJS);

  @JS('addRRect')
  external JSVoid _addRRect(
    JSFloat32Array rrect,
    JSBoolean counterClockWise,
  );
  void addRRect(
    Float32List rrect,
    bool counterClockWise,
  ) => _addRRect(rrect.toJS, counterClockWise.toJS);

  @JS('addRect')
  external JSVoid _addRect(
    JSFloat32Array rect,
  );
  void addRect(
    Float32List rect,
  ) => _addRect(rect.toJS);

  @JS('arcToOval')
  external JSVoid _arcToOval(
    JSFloat32Array oval,
    JSNumber startAngleDegrees,
    JSNumber sweepAngleDegrees,
    JSBoolean forceMoveTo,
  );
  void arcToOval(
    Float32List oval,
    double startAngleDegrees,
    double sweepAngleDegrees,
    bool forceMoveTo,
  ) => _arcToOval(oval.toJS, startAngleDegrees.toJS, sweepAngleDegrees.toJS,
                  forceMoveTo.toJS);

  @JS('arcToRotated')
  external JSVoid _arcToRotated(
    JSNumber radiusX,
    JSNumber radiusY,
    JSNumber rotation,
    JSBoolean useSmallArc,
    JSBoolean counterClockWise,
    JSNumber x,
    JSNumber y,
  );
  void arcToRotated(
    double radiusX,
    double radiusY,
    double rotation,
    bool useSmallArc,
    bool counterClockWise,
    double x,
    double y,
  ) => _arcToRotated(radiusX.toJS, radiusY.toJS, rotation.toJS,
                     useSmallArc.toJS, counterClockWise.toJS,
                     x.toJS, y.toJS);

  external JSVoid close();

  @JS('conicTo')
  external JSVoid _conicTo(
    JSNumber x1,
    JSNumber y1,
    JSNumber x2,
    JSNumber y2,
    JSNumber w,
  );
  void conicTo(
    double x1,
    double y1,
    double x2,
    double y2,
    double w,
  ) => _conicTo(x1.toJS, y1.toJS, x2.toJS, y2.toJS, w.toJS);

  @JS('contains')
  external JSBoolean _contains(
    JSNumber x,
    JSNumber y,
  );
  bool contains(
    double x,
    double y,
  ) => _contains(x.toJS, y.toJS).toDart;

  @JS('cubicTo')
  external JSVoid _cubicTo(
    JSNumber x1,
    JSNumber y1,
    JSNumber x2,
    JSNumber y2,
    JSNumber x3,
    JSNumber y3,
  );
  void cubicTo(
    double x1,
    double y1,
    double x2,
    double y2,
    double x3,
    double y3,
  ) => _cubicTo(x1.toJS, y1.toJS, x2.toJS, y2.toJS, x3.toJS, y3.toJS);

  @JS('getBounds')
  external JSFloat32Array _getBounds();
  Float32List getBounds() => _getBounds().toDart;

  @JS('lineTo')
  external JSVoid _lineTo(JSNumber x, JSNumber y);
  void lineTo(double x, double y) => _lineTo(x.toJS, y.toJS);

  @JS('moveTo')
  external JSVoid _moveTo(JSNumber x, JSNumber y);
  void moveTo(double x, double y) => _moveTo(x.toJS, y.toJS);

  @JS('quadTo')
  external JSVoid _quadTo(
    JSNumber x1,
    JSNumber y1,
    JSNumber x2,
    JSNumber y2,
  );
  void quadTo(
    double x1,
    double y1,
    double x2,
    double y2,
  ) => _quadTo(x1.toJS, y1.toJS, x2.toJS, y2.toJS);

  @JS('rArcTo')
  external JSVoid _rArcTo(
    JSNumber x,
    JSNumber y,
    JSNumber rotation,
    JSBoolean useSmallArc,
    JSBoolean counterClockWise,
    JSNumber deltaX,
    JSNumber deltaY,
  );
  void rArcTo(
    double x,
    double y,
    double rotation,
    bool useSmallArc,
    bool counterClockWise,
    double deltaX,
    double deltaY,
  ) => _rArcTo(x.toJS, y.toJS, rotation.toJS, useSmallArc.toJS,
               counterClockWise.toJS, deltaX.toJS, deltaY.toJS);

  @JS('rConicTo')
  external JSVoid _rConicTo(
    JSNumber x1,
    JSNumber y1,
    JSNumber x2,
    JSNumber y2,
    JSNumber w,
  );
  void rConicTo(
    double x1,
    double y1,
    double x2,
    double y2,
    double w,
  ) => _rConicTo(x1.toJS, y1.toJS, x2.toJS, y2.toJS, w.toJS);

  @JS('rCubicTo')
  external JSVoid _rCubicTo(
    JSNumber x1,
    JSNumber y1,
    JSNumber x2,
    JSNumber y2,
    JSNumber x3,
    JSNumber y3,
  );
  void rCubicTo(
    double x1,
    double y1,
    double x2,
    double y2,
    double x3,
    double y3,
  ) => _rCubicTo(x1.toJS, y1.toJS, x2.toJS, y2.toJS, x3.toJS, y3.toJS);

  @JS('rLineTo')
  external JSVoid _rLineTo(JSNumber x, JSNumber y);
  void rLineTo(double x, double y) => _rLineTo(x.toJS, y.toJS);

  @JS('rMoveTo')
  external JSVoid _rMoveTo(JSNumber x, JSNumber y);
  void rMoveTo(double x, double y) => _rMoveTo(x.toJS, y.toJS);

  @JS('rQuadTo')
  external JSVoid _rQuadTo(
    JSNumber x1,
    JSNumber y1,
    JSNumber x2,
    JSNumber y2,
  );
  void rQuadTo(
    double x1,
    double y1,
    double x2,
    double y2,
  ) => _rQuadTo(x1.toJS, y1.toJS, x2.toJS, y2.toJS);

  external JSVoid reset();

  @JS('toSVGString')
  external JSString _toSVGString();
  String toSVGString() => _toSVGString().toDart;

  @JS('isEmpty')
  external JSBoolean _isEmpty();
  bool isEmpty() => _isEmpty().toDart;

  external SkPath copy();

  @JS('transform')
  external JSVoid _transform(
    JSNumber scaleX,
    JSNumber skewX,
    JSNumber transX,
    JSNumber skewY,
    JSNumber scaleY,
    JSNumber transY,
    JSNumber pers0,
    JSNumber pers1,
    JSNumber pers2,
  );
  void transform(
    double scaleX,
    double skewX,
    double transX,
    double skewY,
    double scaleY,
    double transY,
    double pers0,
    double pers1,
    double pers2,
  ) => _transform(scaleX.toJS, skewX.toJS, transX.toJS,
                  skewY.toJS, scaleY.toJS, transY.toJS,
                  pers0.toJS, pers1.toJS, pers2.toJS);

  /// Serializes the path into a list of commands.
  ///
  /// The list can be used to create a new [SkPath] using
  /// [CanvasKit.Path.MakeFromCmds].
  @JS('toCmds')
  external JSAny _toCmds();
  List<dynamic> toCmds() => _toCmds().toObjectShallow as List<dynamic>;

  external JSVoid delete();
}

@JS('window.flutterCanvasKit.ContourMeasureIter')
@staticInterop
class SkContourMeasureIter {
  factory SkContourMeasureIter(
      SkPath path,
      bool forceClosed,
      double resScale) => SkContourMeasureIter._(path, forceClosed.toJS,
      resScale.toJS);
  external factory SkContourMeasureIter._(
      SkPath path,
      JSBoolean forceClosed,
      JSNumber resScale);
}

extension SkContourMeasureIterExtension on SkContourMeasureIter {
  external SkContourMeasure? next();
  external JSVoid delete();
}

@JS()
@staticInterop
class SkContourMeasure {}

extension SkContourMeasureExtension on SkContourMeasure {
  @JS('getSegment')
  external SkPath _getSegment(
      JSNumber start, JSNumber end, JSBoolean startWithMoveTo);
  SkPath getSegment(double start, double end, bool startWithMoveTo) =>
      _getSegment(start.toJS, end.toJS, startWithMoveTo.toJS);

  @JS('getPosTan')
  external JSFloat32Array _getPosTan(JSNumber distance);
  Float32List getPosTan(double distance) =>
      _getPosTan(distance.toJS).toDart;

  @JS('isClosed')
  external JSBoolean _isClosed();
  bool isClosed() => _isClosed().toDart;

  @JS('length')
  external JSNumber _length();
  double length() => _length().toDartDouble;

  external JSVoid delete();
}

// TODO(hterkelsen): Use a shared malloc'ed array for performance.
Float32List toSkRect(ui.Rect rect) {
  final Float32List skRect = Float32List(4);
  skRect[0] = rect.left;
  skRect[1] = rect.top;
  skRect[2] = rect.right;
  skRect[3] = rect.bottom;
  return skRect;
}

ui.Rect fromSkRect(Float32List skRect) {
  return ui.Rect.fromLTRB(skRect[0], skRect[1], skRect[2], skRect[3]);
}

ui.Rect rectFromSkIRect(Int32List skIRect) {
  return ui.Rect.fromLTRB(
    skIRect[0].toDouble(),
    skIRect[1].toDouble(),
    skIRect[2].toDouble(),
    skIRect[3].toDouble(),
  );
}

// TODO(hterkelsen): Use a shared malloc'ed array for performance.
Float32List toSkRRect(ui.RRect rrect) {
  final Float32List skRRect = Float32List(12);
  skRRect[0] = rrect.left;
  skRRect[1] = rrect.top;
  skRRect[2] = rrect.right;
  skRRect[3] = rrect.bottom;
  skRRect[4] = rrect.tlRadiusX;
  skRRect[5] = rrect.tlRadiusY;
  skRRect[6] = rrect.trRadiusX;
  skRRect[7] = rrect.trRadiusY;
  skRRect[8] = rrect.brRadiusX;
  skRRect[9] = rrect.brRadiusY;
  skRRect[10] = rrect.blRadiusX;
  skRRect[11] = rrect.blRadiusY;
  return skRRect;
}

// TODO(hterkelsen): Use a shared malloc'ed array for performance.
Float32List toOuterSkRect(ui.RRect rrect) {
  final Float32List skRect = Float32List(4);
  skRect[0] = rrect.left;
  skRect[1] = rrect.top;
  skRect[2] = rrect.right;
  skRect[3] = rrect.bottom;
  return skRect;
}

/// Encodes a list of offsets to CanvasKit-compatible point array.
///
/// Uses `CanvasKit.Malloc` to allocate storage for the points in the WASM
/// memory to avoid unnecessary copying. Unless CanvasKit takes ownership of
/// the list the returned list must be explicitly freed using
/// [free].
SkFloat32List toMallocedSkPoints(List<ui.Offset> points) {
  final int len = points.length;
  final SkFloat32List skPoints = mallocFloat32List(len * 2);
  final Float32List list = skPoints.toTypedArray();
  for (int i = 0; i < len; i++) {
    list[2 * i] = points[i].dx;
    list[2 * i + 1] = points[i].dy;
  }
  return skPoints;
}

/// Converts a list of [ui.Offset] into a flat list of points.
Float32List toFlatSkPoints(List<ui.Offset> points) {
  final int len = points.length;
  final Float32List result = Float32List(len * 2);
  for (int i = 0; i < len; i++) {
    result[2 * i] = points[i].dx;
    result[2 * i + 1] = points[i].dy;
  }
  return result;
}

/// Converts a list of [ui.Color] into a flat list of ints.
Uint32List toFlatColors(List<ui.Color> colors) {
  final int len = colors.length;
  final Uint32List result = Uint32List(len);
  for (int i = 0; i < len; i++) {
    result[i] = colors[i].value;
  }
  return result;
}

Uint16List toUint16List(List<int> ints) {
  final int len = ints.length;
  final Uint16List result = Uint16List(len);
  for (int i = 0; i < len; i++) {
    result[i] = ints[i];
  }
  return result;
}

@JS('window.flutterCanvasKit.PictureRecorder')
@staticInterop
class SkPictureRecorder {
  external factory SkPictureRecorder();
}

extension SkPictureRecorderExtension on SkPictureRecorder {
  @JS('beginRecording')
  external SkCanvas _beginRecording(
      JSFloat32Array bounds, JSBoolean computeBounds);
  SkCanvas beginRecording(Float32List bounds) =>
      _beginRecording(bounds.toJS, true.toJS);

  external SkPicture finishRecordingAsPicture();
  external JSVoid delete();
}

/// We do not use the `delete` method (which may be removed in the future anyway).
///
/// By Skia coding convention raw pointers should always be treated as
/// "borrowed", i.e. their memory is managed by other objects. In the case of
/// [SkCanvas] it is managed by [SkPictureRecorder].
@JS()
@anonymous
@staticInterop
class SkCanvas {}

extension SkCanvasExtension on SkCanvas {
  @JS('clear')
  external JSVoid _clear(JSFloat32Array color);
  void clear(Float32List color) => _clear(color.toJS);

  @JS('clipPath')
  external JSVoid _clipPath(
    SkPath path,
    SkClipOp clipOp,
    JSBoolean doAntiAlias,
  );
  void clipPath(
    SkPath path,
    SkClipOp clipOp,
    bool doAntiAlias,
  ) => _clipPath(path, clipOp, doAntiAlias.toJS);

  @JS('clipRRect')
  external JSVoid _clipRRect(
    JSFloat32Array rrect,
    SkClipOp clipOp,
    JSBoolean doAntiAlias,
  );
  void clipRRect(
    Float32List rrect,
    SkClipOp clipOp,
    bool doAntiAlias,
  ) => _clipRRect(rrect.toJS, clipOp, doAntiAlias.toJS);

  @JS('clipRect')
  external JSVoid _clipRect(
    JSFloat32Array rect,
    SkClipOp clipOp,
    JSBoolean doAntiAlias,
  );
  void clipRect(
    Float32List rect,
    SkClipOp clipOp,
    bool doAntiAlias,
  ) => _clipRect(rect.toJS, clipOp, doAntiAlias.toJS);

  @JS('getDeviceClipBounds')
  external JSInt32Array _getDeviceClipBounds();
  Int32List getDeviceClipBounds() => _getDeviceClipBounds().toDart;

  @JS('drawArc')
  external JSVoid _drawArc(
    JSFloat32Array oval,
    JSNumber startAngleDegrees,
    JSNumber sweepAngleDegrees,
    JSBoolean useCenter,
    SkPaint paint,
  );
  void drawArc(
    Float32List oval,
    double startAngleDegrees,
    double sweepAngleDegrees,
    bool useCenter,
    SkPaint paint,
  ) => _drawArc(oval.toJS, startAngleDegrees.toJS, sweepAngleDegrees.toJS,
                useCenter.toJS, paint);

  @JS('drawAtlas')
  external JSVoid _drawAtlas(
    SkImage image,
    JSFloat32Array rects,
    JSFloat32Array rstTransforms,
    SkPaint paint,
    SkBlendMode blendMode,
    JSUint32Array? colors,
  );
  void drawAtlas(
    SkImage image,
    Float32List rects,
    Float32List rstTransforms,
    SkPaint paint,
    SkBlendMode blendMode,
    Uint32List? colors,
  ) => _drawAtlas(image, rects.toJS, rstTransforms.toJS, paint,
                  blendMode, colors?.toJS);

  @JS('drawCircle')
  external JSVoid _drawCircle(
    JSNumber x,
    JSNumber y,
    JSNumber radius,
    SkPaint paint,
  );
  void drawCircle(
    double x,
    double y,
    double radius,
    SkPaint paint,
  ) => _drawCircle(x.toJS, y.toJS, radius.toJS, paint);

  @JS('drawColorInt')
  external JSVoid _drawColorInt(
    JSNumber color,
    SkBlendMode blendMode,
  );
  void drawColorInt(
    double color,
    SkBlendMode blendMode,
  ) => _drawColorInt(color.toJS, blendMode);

  @JS('drawDRRect')
  external JSVoid _drawDRRect(
    JSFloat32Array outer,
    JSFloat32Array inner,
    SkPaint paint,
  );
  void drawDRRect(
    Float32List outer,
    Float32List inner,
    SkPaint paint,
  ) => _drawDRRect(outer.toJS, inner.toJS, paint);

  @JS('drawImageCubic')
  external JSVoid _drawImageCubic(
    SkImage image,
    JSNumber x,
    JSNumber y,
    JSNumber B,
    JSNumber C,
    SkPaint paint,
  );
  void drawImageCubic(
    SkImage image,
    double x,
    double y,
    double B,
    double C,
    SkPaint paint,
  ) => _drawImageCubic(image, x.toJS, y.toJS, B.toJS, C.toJS, paint);

  @JS('drawImageOptions')
  external JSVoid _drawImageOptions(
    SkImage image,
    JSNumber x,
    JSNumber y,
    SkFilterMode filterMode,
    SkMipmapMode mipmapMode,
    SkPaint paint,
  );
  void drawImageOptions(
    SkImage image,
    double x,
    double y,
    SkFilterMode filterMode,
    SkMipmapMode mipmapMode,
    SkPaint paint,
  ) => _drawImageOptions(image, x.toJS, y.toJS, filterMode, mipmapMode, paint);

  @JS('drawImageRectCubic')
  external JSVoid _drawImageRectCubic(
    SkImage image,
    JSFloat32Array src,
    JSFloat32Array dst,
    JSNumber B,
    JSNumber C,
    SkPaint paint,
  );
  void drawImageRectCubic(
    SkImage image,
    Float32List src,
    Float32List dst,
    double B,
    double C,
    SkPaint paint,
  ) => _drawImageRectCubic(image, src.toJS, dst.toJS, B.toJS, C.toJS, paint);

  @JS('drawImageRectOptions')
  external JSVoid _drawImageRectOptions(
    SkImage image,
    JSFloat32Array src,
    JSFloat32Array dst,
    SkFilterMode filterMode,
    SkMipmapMode mipmapMode,
    SkPaint paint,
  );
  void drawImageRectOptions(
    SkImage image,
    Float32List src,
    Float32List dst,
    SkFilterMode filterMode,
    SkMipmapMode mipmapMode,
    SkPaint paint,
  ) => _drawImageRectOptions(image, src.toJS, dst.toJS, filterMode, mipmapMode,
                             paint);

  @JS('drawImageNine')
  external JSVoid _drawImageNine(
    SkImage image,
    JSFloat32Array center,
    JSFloat32Array dst,
    SkFilterMode filterMode,
    SkPaint paint,
  );
  void drawImageNine(
    SkImage image,
    Float32List center,
    Float32List dst,
    SkFilterMode filterMode,
    SkPaint paint,
  ) => _drawImageNine(image, center.toJS, dst.toJS, filterMode, paint);

  @JS('drawLine')
  external JSVoid _drawLine(
    JSNumber x1,
    JSNumber y1,
    JSNumber x2,
    JSNumber y2,
    SkPaint paint,
  );
  void drawLine(
    double x1,
    double y1,
    double x2,
    double y2,
    SkPaint paint,
  ) => _drawLine(x1.toJS, y1.toJS, x2.toJS, y2.toJS, paint);

  @JS('drawOval')
  external JSVoid _drawOval(
    JSFloat32Array rect,
    SkPaint paint,
  );
  void drawOval(
    Float32List rect,
    SkPaint paint,
  ) => _drawOval(rect.toJS, paint);

  external JSVoid drawPaint(
    SkPaint paint,
  );
  external JSVoid drawPath(
    SkPath path,
    SkPaint paint,
  );

  @JS('drawPoints')
  external JSVoid _drawPoints(
    SkPointMode pointMode,
    JSFloat32Array points,
    SkPaint paint,
  );
  void drawPoints(
    SkPointMode pointMode,
    Float32List points,
    SkPaint paint,
  ) => _drawPoints(pointMode, points.toJS, paint);

  @JS('drawRRect')
  external JSVoid _drawRRect(
    JSFloat32Array rrect,
    SkPaint paint,
  );
  void drawRRect(
    Float32List rrect,
    SkPaint paint,
  ) => _drawRRect(rrect.toJS, paint);

  @JS('drawRect')
  external JSVoid _drawRect(
    JSFloat32Array rect,
    SkPaint paint,
  );
  void drawRect(
    Float32List rect,
    SkPaint paint,
  ) => _drawRect(rect.toJS, paint);

  @JS('drawShadow')
  external JSVoid _drawShadow(
    SkPath path,
    JSFloat32Array zPlaneParams,
    JSFloat32Array lightPos,
    JSNumber lightRadius,
    JSFloat32Array ambientColor,
    JSFloat32Array spotColor,
    JSNumber flags,
  );
  void drawShadow(
    SkPath path,
    Float32List zPlaneParams,
    Float32List lightPos,
    double lightRadius,
    Float32List ambientColor,
    Float32List spotColor,
    double flags,
  ) => _drawShadow(path, zPlaneParams.toJS, lightPos.toJS, lightRadius.toJS,
                   ambientColor.toJS, spotColor.toJS, flags.toJS);

  external JSVoid drawVertices(
    SkVertices vertices,
    SkBlendMode blendMode,
    SkPaint paint,
  );

  @JS('save')
  external JSNumber _save();
  double save() => _save().toDartDouble;

  @JS('getSaveCount')
  external JSNumber _getSaveCount();
  double getSaveCount() => _getSaveCount().toDartDouble;

  @JS('saveLayer')
  external JSVoid _saveLayer(
    SkPaint? paint,
    JSFloat32Array? bounds,
    SkImageFilter? backdrop,
    JSNumber? flags,
  );
  void saveLayer(
    SkPaint? paint,
    Float32List? bounds,
    SkImageFilter? backdrop,
    int? flags,
  ) => _saveLayer(paint, bounds?.toJS, backdrop, flags?.toJS);

  external JSVoid restore();

  @JS('restoreToCount')
  external JSVoid _restoreToCount(JSNumber count);
  void restoreToCount(double count) => _restoreToCount(count.toJS);

  @JS('rotate')
  external JSVoid _rotate(
    JSNumber angleDegrees,
    JSNumber px,
    JSNumber py,
  );
  void rotate(
    double angleDegrees,
    double px,
    double py,
  ) => _rotate(angleDegrees.toJS, px.toJS, py.toJS);

  @JS('scale')
  external JSVoid _scale(JSNumber x, JSNumber y);
  void scale(double x, double y) => _scale(x.toJS, y.toJS);

  @JS('skew')
  external JSVoid _skew(JSNumber x, JSNumber y);
  void skew(double x, double y) => _skew(x.toJS, y.toJS);

  @JS('concat')
  external JSVoid _concat(JSFloat32Array matrix);
  void concat(Float32List matrix) => _concat(matrix.toJS);

  @JS('translate')
  external JSVoid _translate(JSNumber x, JSNumber y);
  void translate(double x, double y) => _translate(x.toJS, y.toJS);

  @JS('getLocalToDevice')
  external JSAny _getLocalToDevice();
  List<dynamic> getLocalToDevice() => _getLocalToDevice().toObjectShallow as
      List<dynamic>;

  external JSVoid drawPicture(SkPicture picture);

  @JS('drawParagraph')
  external JSVoid _drawParagraph(
    SkParagraph paragraph,
    JSNumber x,
    JSNumber y,
  );
  void drawParagraph(
    SkParagraph paragraph,
    double x,
    double y,
  ) => _drawParagraph(paragraph, x.toJS, y.toJS);
}

@JS()
@anonymous
@staticInterop
class SkPicture {}

extension SkPictureExtension on SkPicture {
  external JSVoid delete();

  @JS('cullRect')
  external JSFloat32Array _cullRect();
  Float32List cullRect() => _cullRect().toDart;

  @JS('approximateBytesUsed')
  external JSNumber _approximateBytesUsed();
  int approximateBytesUsed() => _approximateBytesUsed().toDartInt;
}

@JS()
@anonymous
@staticInterop
class SkParagraphBuilderNamespace {}

extension SkParagraphBuilderNamespaceExtension on SkParagraphBuilderNamespace {
  external SkParagraphBuilder MakeFromFontCollection(
    SkParagraphStyle paragraphStyle,
    SkFontCollection? fontCollection,
  );

  bool RequiresClientICU() {
    if (!js_util.hasProperty(this, 'RequiresClientICU')) {
      return false;
    }
    return js_util.callMethod(this, 'RequiresClientICU', const <Object>[],) as bool;
  }
}

@JS()
@anonymous
@staticInterop
class SkParagraphBuilder {}

extension SkParagraphBuilderExtension on SkParagraphBuilder {
  @JS('addText')
  external JSVoid _addText(JSString text);
  void addText(String text) => _addText(text.toJS);

  external JSVoid pushStyle(SkTextStyle textStyle);
  external JSVoid pushPaintStyle(
      SkTextStyle textStyle, SkPaint foreground, SkPaint background);
  external JSVoid pop();

  @JS('addPlaceholder')
  external JSVoid _addPlaceholder(
    JSNumber width,
    JSNumber height,
    SkPlaceholderAlignment alignment,
    SkTextBaseline baseline,
    JSNumber offset,
  );
  void addPlaceholder(
    double width,
    double height,
    SkPlaceholderAlignment alignment,
    SkTextBaseline baseline,
    double offset,
  ) => _addPlaceholder(width.toJS, height.toJS, alignment,
                       baseline, offset.toJS);

  @JS('getText')
  external JSString _getTextUtf8();
  String getTextUtf8() => _getTextUtf8().toDart;
  // SkParagraphBuilder.getText() returns a utf8 string, we need to decode it
  // into a utf16 string.
  String getText() => utf8.decode(getTextUtf8().codeUnits);

  @JS('setWordsUtf8')
  external JSVoid _setWordsUtf8(JSUint32Array words);
  void setWordsUtf8(Uint32List words) => _setWordsUtf8(words.toJS);

  @JS('setWordsUtf16')
  external JSVoid _setWordsUtf16(JSUint32Array words);
  void setWordsUtf16(Uint32List words) => _setWordsUtf16(words.toJS);

  @JS('setGraphemeBreaksUtf8')
  external JSVoid _setGraphemeBreaksUtf8(JSUint32Array graphemes);
  void setGraphemeBreaksUtf8(Uint32List graphemes) =>
      _setGraphemeBreaksUtf8(graphemes.toJS);

  @JS('setGraphemeBreaksUtf16')
  external JSVoid _setGraphemeBreaksUtf16(JSUint32Array graphemes);
  void setGraphemeBreaksUtf16(Uint32List graphemes) =>
      _setGraphemeBreaksUtf16(graphemes.toJS);

  @JS('setLineBreaksUtf8')
  external JSVoid _setLineBreaksUtf8(JSUint32Array lineBreaks);
  void setLineBreaksUtf8(Uint32List lineBreaks) =>
      _setLineBreaksUtf8(lineBreaks.toJS);

  @JS('setLineBreaksUtf16')
  external JSVoid _setLineBreaksUtf16(JSUint32Array lineBreaks);
  void setLineBreaksUtf16(Uint32List lineBreaks) =>
      _setLineBreaksUtf16(lineBreaks.toJS);

  external SkParagraph build();
  external JSVoid delete();
}

@JS()
@anonymous
@staticInterop
class SkParagraphStyle {}

@JS()
@anonymous
@staticInterop
class SkParagraphStyleProperties {
  external factory SkParagraphStyleProperties();
}

extension SkParagraphStylePropertiesExtension on SkParagraphStyleProperties {
  external set textAlign(SkTextAlign? value);
  external set textDirection(SkTextDirection? value);

  @JS('heightMultiplier')
  external set _heightMultiplier(JSNumber? value);
  set heightMultiplier(double? value) => _heightMultiplier = value?.toJS;

  external set textHeightBehavior(SkTextHeightBehavior? value);

  @JS('maxLines')
  external set _maxLines(JSNumber? value);
  set maxLines(int? value) => _maxLines = value?.toJS;

  @JS('ellipsis')
  external set _ellipsis(JSString? value);
  set ellipsis(String? value) => _ellipsis = value?.toJS;

  external set textStyle(SkTextStyleProperties? value);
  external set strutStyle(SkStrutStyleProperties? strutStyle);

  @JS('replaceTabCharacters')
  external set _replaceTabCharacters(JSBoolean? bool);
  set replaceTabCharacters(bool? bool) => _replaceTabCharacters = bool?.toJS;

  external set applyRoundingHack(bool applyRoundingHack);
}

@JS()
@staticInterop
class SkTextStyle {}

@JS()
@staticInterop
class SkTextDecorationStyleEnum {}

extension SkTextDecorationStyleEnumExtension on SkTextDecorationStyleEnum {
  external SkTextDecorationStyle get Solid;
  external SkTextDecorationStyle get Double;
  external SkTextDecorationStyle get Dotted;
  external SkTextDecorationStyle get Dashed;
  external SkTextDecorationStyle get Wavy;
}

@JS()
@staticInterop
class SkTextDecorationStyle {}

extension SkTextDecorationStyleExtension on SkTextDecorationStyle {
  @JS('value')
  external JSNumber get _value;
  double get value => _value.toDartDouble;
}

final List<SkTextDecorationStyle> _skTextDecorationStyles =
    <SkTextDecorationStyle>[
  canvasKit.DecorationStyle.Solid,
  canvasKit.DecorationStyle.Double,
  canvasKit.DecorationStyle.Dotted,
  canvasKit.DecorationStyle.Dashed,
  canvasKit.DecorationStyle.Wavy,
];

SkTextDecorationStyle toSkTextDecorationStyle(ui.TextDecorationStyle style) {
  return _skTextDecorationStyles[style.index];
}

@JS()
@staticInterop
class SkTextBaselineEnum {}

extension SkTextBaselineEnumExtension on SkTextBaselineEnum {
  external SkTextBaseline get Alphabetic;
  external SkTextBaseline get Ideographic;
}

@JS()
@staticInterop
class SkTextBaseline {}

extension SkTextBaselineExtension on SkTextBaseline {
  @JS('value')
  external JSNumber get _value;
  double get value => _value.toDartDouble;
}

final List<SkTextBaseline> _skTextBaselines = <SkTextBaseline>[
  canvasKit.TextBaseline.Alphabetic,
  canvasKit.TextBaseline.Ideographic,
];

SkTextBaseline toSkTextBaseline(ui.TextBaseline baseline) {
  return _skTextBaselines[baseline.index];
}

@JS()
@staticInterop
class SkPlaceholderAlignmentEnum {}

extension SkPlaceholderAlignmentEnumExtension on SkPlaceholderAlignmentEnum {
  external SkPlaceholderAlignment get Baseline;
  external SkPlaceholderAlignment get AboveBaseline;
  external SkPlaceholderAlignment get BelowBaseline;
  external SkPlaceholderAlignment get Top;
  external SkPlaceholderAlignment get Bottom;
  external SkPlaceholderAlignment get Middle;
}

@JS()
@staticInterop
class SkPlaceholderAlignment {}

extension SkPlaceholderAlignmentExtension on SkPlaceholderAlignment {
  @JS('value')
  external JSNumber get _value;
  double get value => _value.toDartDouble;
}

final List<SkPlaceholderAlignment> _skPlaceholderAlignments =
    <SkPlaceholderAlignment>[
  canvasKit.PlaceholderAlignment.Baseline,
  canvasKit.PlaceholderAlignment.AboveBaseline,
  canvasKit.PlaceholderAlignment.BelowBaseline,
  canvasKit.PlaceholderAlignment.Top,
  canvasKit.PlaceholderAlignment.Bottom,
  canvasKit.PlaceholderAlignment.Middle,
];

SkPlaceholderAlignment toSkPlaceholderAlignment(
    ui.PlaceholderAlignment alignment) {
  return _skPlaceholderAlignments[alignment.index];
}

@JS()
@anonymous
@staticInterop
class SkTextStyleProperties {
  external factory SkTextStyleProperties();
}

extension SkTextStylePropertiesExtension on SkTextStyleProperties {
  @JS('backgroundColor')
  external set _backgroundColor(JSFloat32Array? value);
  set backgroundColor(Float32List? value) =>
      _backgroundColor = value?.toJS;

  @JS('color')
  external set _color(JSFloat32Array? value);
  set color(Float32List? value) => _color = value?.toJS;

  @JS('foregroundColor')
  external set _foregroundColor(JSFloat32Array? value);
  set foregroundColor(Float32List? value) => _foregroundColor = value?.toJS;

  @JS('decoration')
  external set _decoration(JSNumber? value);
  set decoration(int? value) => _decoration = value?.toJS;

  @JS('decorationThickness')
  external set _decorationThickness(JSNumber? value);
  set decorationThickness(double? value) =>
      _decorationThickness = value?.toJS;

  @JS('decorationColor')
  external set _decorationColor(JSFloat32Array? value);
  set decorationColor(Float32List? value) => _decorationColor = value?.toJS;

  external set decorationStyle(SkTextDecorationStyle? value);
  external set textBaseline(SkTextBaseline? value);

  @JS('fontSize')
  external set _fontSize(JSNumber? value);
  set fontSize(double? value) => _fontSize = value?.toJS;

  @JS('letterSpacing')
  external set _letterSpacing(JSNumber? value);
  set letterSpacing(double? value) => _letterSpacing = value?.toJS;

  @JS('wordSpacing')
  external set _wordSpacing(JSNumber? value);
  set wordSpacing(double? value) => _wordSpacing = value?.toJS;

  @JS('heightMultiplier')
  external set _heightMultiplier(JSNumber? value);
  set heightMultiplier(double? value) => _heightMultiplier = value?.toJS;

  @JS('halfLeading')
  external set _halfLeading(JSBoolean? value);
  set halfLeading(bool? value) => _halfLeading = value?.toJS;

  @JS('locale')
  external set _locale(JSString? value);
  set locale(String? value) => _locale = value?.toJS;

  @JS('fontFamilies')
  external set _fontFamilies(JSAny? value);
  set fontFamilies(List<String>? value) => _fontFamilies = value?.toJSAnyShallow;

  external set fontStyle(SkFontStyle? value);

  @JS('shadows')
  external set _shadows(JSArray? value);
  set shadows(List<SkTextShadow>? value) =>
      // TODO(joshualitt): remove this cast when we reify JS types on JS
      // backends.
      // ignore: unnecessary_cast
      _shadows = (value as List<JSAny>?)?.toJS;

  @JS('fontFeatures')
  external set _fontFeatures(JSArray? value);
  set fontFeatures(List<SkFontFeature>? value) =>
      // TODO(joshualitt): remove this cast when we reify JS types on JS
      // backends.
      // ignore: unnecessary_cast
      _fontFeatures = (value as List<JSAny>?)?.toJS;

  @JS('fontVariations')
  external set _fontVariations(JSArray? value);
  set fontVariations(List<SkFontVariation>? value) =>
      // TODO(joshualitt): remove this cast when we reify JS types on JS
      // backends.
      // ignore: unnecessary_cast
      _fontVariations = (value as List<JSAny>?)?.toJS;
}

@JS()
@anonymous
@staticInterop
class SkStrutStyleProperties {
  external factory SkStrutStyleProperties();
}

extension SkStrutStylePropertiesExtension on SkStrutStyleProperties {
  @JS('fontFamilies')
  external set _fontFamilies(JSAny? value);
  set fontFamilies(List<String>? value) =>
      _fontFamilies = value?.toJSAnyShallow;

  external set fontStyle(SkFontStyle? value);

  @JS('fontSize')
  external set _fontSize(JSNumber? value);
  set fontSize(double? value) => _fontSize = value?.toJS;

  @JS('heightMultiplier')
  external set _heightMultiplier(JSNumber? value);
  set heightMultiplier(double? value) => _heightMultiplier = value?.toJS;

  @JS('halfLeading')
  external set _halfLeading(JSBoolean? value);
  set halfLeading(bool? value) => _halfLeading = value?.toJS;

  @JS('leading')
  external set _leading(JSNumber? value);
  set leading(double? value) => _leading = value?.toJS;

  @JS('strutEnabled')
  external set _strutEnabled(JSBoolean? value);
  set strutEnabled(bool? value) => _strutEnabled = value?.toJS;

  @JS('forceStrutHeight')
  external set _forceStrutHeight(JSBoolean? value);
  set forceStrutHeight(bool? value) => _forceStrutHeight = value?.toJS;
}

@JS()
@anonymous
@staticInterop
class SkFontStyle {
  external factory SkFontStyle();
}

extension SkFontStyleExtension on SkFontStyle {
  external set weight(SkFontWeight? value);
  external set slant(SkFontSlant? value);
}

@JS()
@anonymous
@staticInterop
class SkTextShadow {
  external factory SkTextShadow();
}

extension SkTextShadowExtension on SkTextShadow {
  @JS('color')
  external set _color(JSFloat32Array? value);
  set color(Float32List? value) => _color = value?.toJS;

  @JS('offset')
  external set _offset(JSFloat32Array? value);
  set offset(Float32List? value) => _offset = value?.toJS;

  @JS('blurRadius')
  external set _blurRadius(JSNumber? value);
  set blurRadius(double? value) => _blurRadius = value?.toJS;
}

@JS()
@anonymous
@staticInterop
class SkFontFeature {
  external factory SkFontFeature();
}

extension SkFontFeatureExtension on SkFontFeature {
  @JS('name')
  external set _name(JSString? value);
  set name(String? value) => _name = value?.toJS;

  @JS('value')
  external set _value(JSNumber? value);
  set value(int? v) => _value = v?.toJS;
}

@JS()
@anonymous
@staticInterop
class SkFontVariation {
  external factory SkFontVariation();
}

extension SkFontVariationExtension on SkFontVariation {
  @JS('axis')
  external set _axis(JSString? value);
  set axis(String? value) => _axis = value?.toJS;

  @JS('value')
  external set _value(JSNumber? value);
  set value(double? v) => _value = v?.toJS;
}

@JS()
@anonymous
@staticInterop
class SkTypeface {}

@JS('window.flutterCanvasKit.Font')
@staticInterop
class SkFont {
  external factory SkFont(SkTypeface typeface);
}

extension SkFontExtension on SkFont {
  @JS('getGlyphIDs')
  external JSUint16Array _getGlyphIDs(JSString text);
  Uint16List getGlyphIDs(String text) => _getGlyphIDs(text.toJS).toDart;

  @JS('getGlyphBounds')
  external JSVoid _getGlyphBounds(
      JSAny glyphs, SkPaint? paint, JSUint8Array? output);
  void getGlyphBounds(
      List<int> glyphs, SkPaint? paint, Uint8List? output) =>
      _getGlyphBounds(glyphs.toJSAnyShallow, paint, output?.toJS);
}

@JS()
@anonymous
@staticInterop
class SkFontMgr {}

extension SkFontMgrExtension on SkFontMgr {
  @JS('getFamilyName')
  external JSString? _getFamilyName(JSNumber fontId);
  String? getFamilyName(double fontId) => _getFamilyName(fontId.toJS)?.toDart;

  external JSVoid delete();

  @JS('MakeTypefaceFromData')
  external SkTypeface? _MakeTypefaceFromData(JSUint8Array font);
  SkTypeface? MakeTypefaceFromData(Uint8List font) =>
      _MakeTypefaceFromData(font.toJS);
}

@JS('window.flutterCanvasKit.TypefaceFontProvider')
@staticInterop
class TypefaceFontProvider extends SkFontMgr {
}

extension TypefaceFontProviderExtension on TypefaceFontProvider {
  @JS('registerFont')
  external JSVoid _registerFont(JSUint8Array font, JSString family);
  void registerFont(Uint8List font, String family) =>
      _registerFont(font.toJS, family.toJS);
}

@JS()
@anonymous
@staticInterop
class SkFontCollection {}

extension SkFontCollectionExtension on SkFontCollection {
  external void enableFontFallback();
  external void setDefaultFontManager(TypefaceFontProvider? fontManager);
  external void delete();
}

@JS()
@anonymous
@staticInterop
class SkLineMetrics {}

extension SkLineMetricsExtension on SkLineMetrics {
  @JS('startIndex')
  external JSNumber get _startIndex;
  double get startIndex => _startIndex.toDartDouble;

  @JS('endIndex')
  external JSNumber get _endIndex;
  double get endIndex => _endIndex.toDartDouble;

  @JS('endExcludingWhitespaces')
  external JSNumber get _endExcludingWhitespaces;
  double get endExcludingWhitespaces => _endExcludingWhitespaces.toDartDouble;

  @JS('endIncludingNewline')
  external JSNumber get _endIncludingNewline;
  double get endIncludingNewline => _endIncludingNewline.toDartDouble;

  @JS('isHardBreak')
  external JSBoolean get _isHardBreak;
  bool get isHardBreak => _isHardBreak.toDart;

  @JS('ascent')
  external JSNumber get _ascent;
  double get ascent => _ascent.toDartDouble;

  @JS('descent')
  external JSNumber get _descent;
  double get descent => _descent.toDartDouble;

  @JS('height')
  external JSNumber get _height;
  double get height => _height.toDartDouble;

  @JS('width')
  external JSNumber get _width;
  double get width => _width.toDartDouble;

  @JS('left')
  external JSNumber get _left;
  double get left => _left.toDartDouble;

  @JS('baseline')
  external JSNumber get _baseline;
  double get baseline => _baseline.toDartDouble;

  @JS('lineNumber')
  external JSNumber get _lineNumber;
  double get lineNumber => _lineNumber.toDartDouble;
}

@JS()
@anonymous
@staticInterop
class SkRectWithDirection {}

extension SkRectWithDirectionExtension on SkRectWithDirection {
  @JS('rect')
  external JSFloat32Array get _rect;
  Float32List get rect => _rect.toDart;

  @JS('rect')
  external set _rect(JSFloat32Array rect);
  set rect(Float32List r) => _rect = r.toJS;

  external SkTextDirection dir;
}

@JS()
@anonymous
@staticInterop
class SkParagraph {}

extension SkParagraphExtension on SkParagraph {
  @JS('getAlphabeticBaseline')
  external JSNumber _getAlphabeticBaseline();
  double getAlphabeticBaseline() => _getAlphabeticBaseline().toDartDouble;

  @JS('didExceedMaxLines')
  external JSBoolean _didExceedMaxLines();
  bool didExceedMaxLines() => _didExceedMaxLines().toDart;

  @JS('getHeight')
  external JSNumber _getHeight();
  double getHeight() => _getHeight().toDartDouble;

  @JS('getIdeographicBaseline')
  external JSNumber _getIdeographicBaseline();
  double getIdeographicBaseline() => _getIdeographicBaseline().toDartDouble;

  @JS('getLineMetrics')
  external JSArray _getLineMetrics();
  List<SkLineMetrics> getLineMetrics() =>
      _getLineMetrics().toDart.cast<SkLineMetrics>();

  @JS('getLongestLine')
  external JSNumber _getLongestLine();
  double getLongestLine() => _getLongestLine().toDartDouble;

  @JS('getMaxIntrinsicWidth')
  external JSNumber _getMaxIntrinsicWidth();
  double getMaxIntrinsicWidth() => _getMaxIntrinsicWidth().toDartDouble;

  @JS('getMinIntrinsicWidth')
  external JSNumber _getMinIntrinsicWidth();
  double getMinIntrinsicWidth() => _getMinIntrinsicWidth().toDartDouble;

  @JS('getMaxWidth')
  external JSNumber _getMaxWidth();
  double getMaxWidth() => _getMaxWidth().toDartDouble;

  @JS('getRectsForRange')
  external JSArray _getRectsForRange(
    JSNumber start,
    JSNumber end,
    SkRectHeightStyle heightStyle,
    SkRectWidthStyle widthStyle,
  );
  List<SkRectWithDirection> getRectsForRange(
    double start,
    double end,
    SkRectHeightStyle heightStyle,
    SkRectWidthStyle widthStyle,
  ) => _getRectsForRange(start.toJS, end.toJS, heightStyle,
                         widthStyle).toDart.cast<SkRectWithDirection>();

  @JS('getRectsForPlaceholders')
  external JSArray _getRectsForPlaceholders();
  List<SkRectWithDirection> getRectsForPlaceholders() =>
      _getRectsForPlaceholders().toDart.cast<SkRectWithDirection>();

  @JS('getGlyphPositionAtCoordinate')
  external SkTextPosition _getGlyphPositionAtCoordinate(
    JSNumber x,
    JSNumber y,
  );
  SkTextPosition getGlyphPositionAtCoordinate(
    double x,
    double y,
  ) => _getGlyphPositionAtCoordinate(x.toJS, y.toJS);

  @JS('getWordBoundary')
  external SkTextRange _getWordBoundary(JSNumber position);
  SkTextRange getWordBoundary(double position) =>
      _getWordBoundary(position.toJS);

  @JS('layout')
  external JSVoid _layout(JSNumber width);
  void layout(double width) => _layout(width.toJS);

  external JSVoid delete();
}

@JS()
@staticInterop
class SkTextPosition {}

extension SkTextPositionExtnsion on SkTextPosition {
  external SkAffinity get affinity;

  @JS('pos')
  external JSNumber get _pos;
  double get pos => _pos.toDartDouble;
}

@JS()
@staticInterop
class SkTextRange {}

extension SkTextRangeExtension on SkTextRange {
  @JS('start')
  external JSNumber get _start;
  double get start => _start.toDartDouble;

  @JS('end')
  external JSNumber get _end;
  double get end => _end.toDartDouble;
}

@JS()
@anonymous
@staticInterop
class SkVertices {}

extension SkVerticesExtension on SkVertices {
  external JSVoid delete();
}

@JS()
@anonymous
@staticInterop
class SkTonalColors {
  factory SkTonalColors({
    required Float32List ambient,
    required Float32List spot,
  }) => SkTonalColors._(ambient: ambient.toJS, spot: spot.toJS);
  external factory SkTonalColors._({
    required JSFloat32Array ambient,
    required JSFloat32Array spot,
  });
}

extension SkTonalColorsExtension on SkTonalColors {
  @JS('ambient')
  external JSFloat32Array get _ambient;
  Float32List get ambient => _ambient.toDart;

  @JS('spot')
  external JSFloat32Array get _spot;
  Float32List get spot => _spot.toDart;
}

@JS()
@staticInterop
class SkFontMgrNamespace {}

extension SkFontMgrNamespaceExtension on SkFontMgrNamespace {
  // TODO(yjbanov): can this be made non-null? It returns null in our unit-tests right now.
  @JS('FromData')
  external SkFontMgr? _FromData(JSAny fonts);
  SkFontMgr? FromData(List<Uint8List> fonts) => _FromData(fonts.toJSAnyShallow);
}

@JS()
@staticInterop
class TypefaceFontProviderNamespace {}

extension TypefaceFontProviderNamespaceExtension on TypefaceFontProviderNamespace {
  external TypefaceFontProvider Make();
}

@JS()
@staticInterop
class FontCollectionNamespace{}

extension FontCollectionNamespaceExtension on FontCollectionNamespace {
  external SkFontCollection Make();
}

@JS()
@anonymous
@staticInterop
class SkTypefaceFactory {}

extension SkTypefaceFactoryExtension on SkTypefaceFactory {
  @JS('MakeFreeTypeFaceFromData')
  external SkTypeface? _MakeFreeTypeFaceFromData(JSArrayBuffer fontData);
  SkTypeface? MakeFreeTypeFaceFromData(ByteBuffer fontData) =>
      _MakeFreeTypeFaceFromData(fontData.toJS);
}

/// Any Skia object that has a `delete` method.
@JS()
@anonymous
@staticInterop
class SkDeletable {}

extension SkDeletableExtension on SkDeletable {
  /// Deletes the C++ side object.
  external JSVoid delete();

  /// Returns whether the corresponding C++ object has been deleted.
  @JS('isDeleted')
  external JSBoolean _isDeleted();
  bool isDeleted() => _isDeleted().toDart;

  /// Returns the JavaScript constructor for this object.
  ///
  /// This is useful for debugging.
  external JsConstructor get constructor;
}

@JS()
@anonymous
@staticInterop
class JsConstructor {}

extension JsConstructorExtension on JsConstructor {
  /// The name of the "constructor", typically the function name called with
  /// the `new` keyword, or the ES6 class name.
  ///
  /// This is useful for debugging.
  @JS('name')
  external JSString get _name;
  String get name => _name.toDart;
}

@JS()
@staticInterop
class SkData {}

extension SkDataExtension on SkData {
  @JS('size')
  external JSNumber _size();
  double size() => _size().toDartDouble;

  @JS('isEmpty')
  external JSBoolean _isEmpty();
  bool isEmpty() => _isEmpty().toDart;

  @JS('bytes')
  external JSUint8Array _bytes();
  Uint8List bytes() => _bytes().toDart;

  external JSVoid delete();
}

@JS()
@anonymous
@staticInterop
class SkImageInfo {
  factory SkImageInfo({
    required double width,
    required double height,
    required SkColorType colorType,
    required SkAlphaType alphaType,
    required ColorSpace colorSpace,
  }) => SkImageInfo._(width: width.toJS,
                      height: height.toJS,
                      colorType: colorType,
                      alphaType: alphaType,
                      colorSpace: colorSpace);
  external factory SkImageInfo._({
    required JSNumber width,
    required JSNumber height,
    required SkColorType colorType,
    required SkAlphaType alphaType,
    required ColorSpace colorSpace,
  });
}

extension SkImageInfoExtension on SkImageInfo {
  external SkAlphaType get alphaType;
  external ColorSpace get colorSpace;
  external SkColorType get colorType;

  @JS('height')
  external JSNumber get _height;
  double get height => _height.toDartDouble;

  @JS('isEmpty')
  external JSBoolean get _isEmpty;
  bool get isEmpty => _isEmpty.toDart;

  @JS('isOpaque')
  external JSBoolean get _isOpaque;
  bool get isOpaque => _isOpaque.toDart;

  @JS('bounds')
  external JSFloat32Array get _bounds;
  Float32List get bounds => _bounds.toDart;

  @JS('width')
  external JSNumber get _width;
  double get width => _width.toDartDouble;

  external SkImageInfo makeAlphaType(SkAlphaType alphaType);
  external SkImageInfo makeColorSpace(ColorSpace colorSpace);
  external SkImageInfo makeColorType(SkColorType colorType);

  @JS('makeWH')
  external SkImageInfo _makeWH(JSNumber width, JSNumber height);
  SkImageInfo makeWH(double width, double height) =>
      _makeWH(width.toJS, height.toJS);
}

@JS()
@anonymous
@staticInterop
class SkPartialImageInfo {
  factory SkPartialImageInfo({
    required double width,
    required double height,
    required SkColorType colorType,
    required SkAlphaType alphaType,
    required ColorSpace colorSpace,
  }) => SkPartialImageInfo._(width: width.toJS,
                             height: height.toJS,
                             colorType: colorType,
                             alphaType: alphaType,
                             colorSpace: colorSpace);
  external factory SkPartialImageInfo._({
    required JSNumber width,
    required JSNumber height,
    required SkColorType colorType,
    required SkAlphaType alphaType,
    required ColorSpace colorSpace,
  });
}

extension SkPartialImageInfoExtension on SkPartialImageInfo {
  external SkAlphaType get alphaType;
  external ColorSpace get colorSpace;
  external SkColorType get colorType;

  @JS('height')
  external JSNumber get _height;
  double get height => _height.toDartDouble;

  @JS('width')
  external JSNumber get _width;
  double get width => _width.toDartDouble;
}

@JS('window.flutterCanvasKit.RuntimeEffect')
@anonymous
@staticInterop
class SkRuntimeEffect {}

@JS('window.flutterCanvasKit.RuntimeEffect.Make')
external SkRuntimeEffect? _MakeRuntimeEffect(JSString program);
SkRuntimeEffect? MakeRuntimeEffect(String program) =>
    _MakeRuntimeEffect(program.toJS);

extension SkSkRuntimeEffectExtension on SkRuntimeEffect {
  @JS('makeShader')
  external SkShader? _makeShader(JSAny uniforms);
  SkShader? makeShader(List<Object> uniforms) =>
      _makeShader(uniforms.toJSAnyShallow);

  @JS('makeShaderWithChildren')
  external SkShader? _makeShaderWithChildren(JSAny uniforms, JSAny children);
  SkShader? makeShaderWithChildren(
      List<Object> uniforms, List<Object?> children) =>
          _makeShaderWithChildren(uniforms.toJSAnyShallow,
              children.toJSAnyShallow);
}

const String _kFullCanvasKitJsFileName = 'canvaskit.js';
const String _kChromiumCanvasKitJsFileName = 'chromium/canvaskit.js';

String get _canvasKitBaseUrl => configuration.canvasKitBaseUrl;

@visibleForTesting
List<String> getCanvasKitJsFileNames(CanvasKitVariant variant) {
  switch (variant) {
    case CanvasKitVariant.auto:
      return <String>[
        if (_enableCanvasKitChromiumInAutoMode) _kChromiumCanvasKitJsFileName,
        _kFullCanvasKitJsFileName,
      ];
    case CanvasKitVariant.full:
      return <String>[_kFullCanvasKitJsFileName];
    case CanvasKitVariant.chromium:
      return <String>[_kChromiumCanvasKitJsFileName];
  }
}
Iterable<String> get _canvasKitJsUrls {
  return getCanvasKitJsFileNames(configuration.canvasKitVariant).map(
    (String filename) => '$_canvasKitBaseUrl$filename',
  );
}
@visibleForTesting
String canvasKitWasmModuleUrl(String file, String canvasKitBase) =>
    canvasKitBase + file;

/// Download and initialize the CanvasKit module.
///
/// Downloads the CanvasKit JavaScript, then calls `CanvasKitInit` to download
/// and intialize the CanvasKit wasm.
Future<CanvasKit> downloadCanvasKit() async {
  await _downloadOneOf(_canvasKitJsUrls);

  final CanvasKit canvasKit = await CanvasKitInit(CanvasKitInitOptions(
    locateFile: createLocateFileCallback(canvasKitWasmModuleUrl),
  ));

  if (canvasKit.ParagraphBuilder.RequiresClientICU() && !browserSupportsCanvaskitChromium) {
    throw Exception(
      'The CanvasKit variant you are using only works on Chromium browsers. '
      'Please use a different CanvasKit variant, or use a Chromium browser.',
    );
  }

  return canvasKit;
}

/// Finds the first URL in [urls] that can be downloaded successfully, and
/// downloads it.
///
/// If none of the URLs can be downloaded, throws an [Exception].
Future<void> _downloadOneOf(Iterable<String> urls) async {
  for (final String url in urls) {
    if (await _downloadCanvasKitJs(url)) {
      return;
    }
  }

  // Reaching this point means that all URLs failed to download.
  throw Exception(
    'Failed to download any of the following CanvasKit URLs: $urls',
  );
}

/// Downloads the CanvasKit JavaScript file at [url].
///
/// Returns a [Future] that completes with `true` if the CanvasKit JavaScript
/// file was successfully downloaded, or `false` if it failed.
Future<bool> _downloadCanvasKitJs(String url) {
  final DomHTMLScriptElement canvasKitScript =
      createDomHTMLScriptElement(configuration.nonce);
  canvasKitScript.src = createTrustedScriptUrl(url);

  final Completer<bool> canvasKitLoadCompleter = Completer<bool>();

  late final DomEventListener loadCallback;
  late final DomEventListener errorCallback;

  void loadEventHandler(DomEvent _) {
    canvasKitScript.remove();
    canvasKitLoadCompleter.complete(true);
  }
  void errorEventHandler(DomEvent errorEvent) {
    canvasKitScript.remove();
    canvasKitLoadCompleter.complete(false);
  }

  loadCallback = createDomEventListener(loadEventHandler);
  errorCallback = createDomEventListener(errorEventHandler);

  canvasKitScript.addEventListener('load', loadCallback);
  canvasKitScript.addEventListener('error', errorCallback);

  domDocument.head!.appendChild(canvasKitScript);

  return canvasKitLoadCompleter.future;
}
