🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@pencil.dev/cli

Package Overview
Dependencies
Maintainers
2
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@pencil.dev/cli - npm Package Compare versions

Comparing version
0.2.2
to
0.2.3
+184
dist/node_modules/@highagency/pencil-skia/bidi.html
<!DOCTYPE html>
<title>CanvasKit Bidi</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
canvas {
border: 1px dashed #AAA;
}
#sampleText {
width: 400px;
height: 200px;
}
</style>
<canvas id="getBidiRegions" width=900 height=800></canvas>
<canvas id="reorderVisuals" width=900 height=800></canvas>
<canvas id="getCodeUnitFlags" width=900 height=800></canvas>
<script type="text/javascript" src="/build/canvaskit.js"></script>
<script type="text/javascript" charset="utf-8">
var cdn = 'https://cdn.skia.org/misc/';
const ckLoaded = CanvasKitInit({locateFile: (file) => '/build/'+file});
const loadRoboto = fetch(cdn + 'Roboto-Regular.ttf').then((response) => response.arrayBuffer());
const loadEmoji = fetch(cdn + 'NotoEmoji.v26.ttf').then((response) => response.arrayBuffer());
const loadArabic = fetch(cdn + 'NotoSansArabic.v18.ttf').then((response) => response.arrayBuffer());
var mallocedLevels;
Promise.all([ckLoaded, loadRoboto]).then(([ck, roboto]) => {
GetBidiRegions(ck, roboto);
ReorderVisuals(ck, roboto);
GetCodeUnitFlags(ck, roboto);
});
function GetBidiRegions(CanvasKit, robotoData) {
if (!robotoData || !CanvasKit) {
return;
}
const surface = CanvasKit.MakeCanvasSurface('getBidiRegions');
if (!surface) {
console.error('Could not make surface');
return;
}
const sampleText = 'left1 يَهْدِيْكُمُ left2 اللَّه left3 ُ وَيُصْلِحُ left4 بَالَكُم';
const roboto = CanvasKit.Typeface.MakeTypefaceFromData(robotoData);
const textPaint = new CanvasKit.Paint();
textPaint.setColor(CanvasKit.RED);
textPaint.setAntiAlias(true);
const dataPaint = new CanvasKit.Paint();
dataPaint.setColor(CanvasKit.BLACK);
dataPaint.setAntiAlias(true);
const textFont = new CanvasKit.Font(roboto, 30);
const dataFont = new CanvasKit.Font(roboto, 20);
function drawFrame(canvas) {
let height = 40;
canvas.drawText(sampleText, 100, height, textPaint, textFont);
height += 80;
const regions = CanvasKit.Bidi.getBidiRegions(sampleText, CanvasKit.TextDirection.LTR);
mallocedLevels = CanvasKit.Malloc(Uint8Array, regions.length);
for (let i = 0; i < regions.length; ++i) {
const region = regions[i];
let result = '[' + region.start + ':' + region.end + '): ';
result += (region.level.value % 2 === 0 ? 'LTR ' : 'RTL ') + region.level;
canvas.drawText(result, 100, height, dataPaint, dataFont);
height += 40;
mallocedLevels[i] = region.level;
}
}
surface.requestAnimationFrame(drawFrame);
return surface;
}
function ReorderVisuals(CanvasKit, robotoData) {
if (!robotoData || !CanvasKit) {
return;
}
const surface = CanvasKit.MakeCanvasSurface('reorderVisuals');
if (!surface) {
console.error('Could not make surface');
return;
}
const roboto = CanvasKit.Typeface.MakeTypefaceFromData(robotoData);
const textPaint = new CanvasKit.Paint();
textPaint.setColor(CanvasKit.RED);
textPaint.setAntiAlias(true);
const dataPaint = new CanvasKit.Paint();
dataPaint.setColor(CanvasKit.BLACK);
dataPaint.setAntiAlias(true);
const textFont = new CanvasKit.Font(roboto, 30);
const dataFont = new CanvasKit.Font(roboto, 20);
function drawCase(canvas, height, input, output) {
let result = '[';
for (let i = 0; i < input.length; ++i) {
const logical = input[i];
result += (i === 0 ? '' : ', ') + logical;
}
result += '] produced: ';
const logicals = CanvasKit.Bidi.reorderVisual(input);
result += '[';
for (let i = 0; i < logicals.length; ++i) {
const logical = logicals[i].index;
result += (i === 0 ? '' : ', ') + logical;
}
result += '] expected: ' + output;
canvas.drawText(result, 100, height, dataPaint, dataFont);
}
function drawFrame(canvas) {
let height = 40;
drawCase(canvas, height, [], '[]');
height += 40;
drawCase(canvas, height, [0], '[0]');
height += 40;
drawCase(canvas, height, [1], '[0]');
height += 40;
drawCase(canvas, height, [0, 1, 0, 1], '[0, 1, 2, 3]');
height += 40;
}
surface.requestAnimationFrame(drawFrame);
return surface;
}
function GetCodeUnitFlags(CanvasKit, robotoData) {
if (!robotoData || !CanvasKit) {
return;
}
const surface = CanvasKit.MakeCanvasSurface('getCodeUnitFlags');
if (!surface) {
console.error('Could not make surface');
return;
}
const flagsText = ' |\u{a0}\u{a0}\u{a0}|\u{0a}\u{0a}\u{0a}|満毎行|';
const roboto = CanvasKit.Typeface.MakeTypefaceFromData(robotoData);
const textPaint = new CanvasKit.Paint();
textPaint.setColor(CanvasKit.RED);
textPaint.setAntiAlias(true);
const dataPaint = new CanvasKit.Paint();
dataPaint.setColor(CanvasKit.BLACK);
dataPaint.setAntiAlias(true);
const textFont = new CanvasKit.Font(roboto, 30);
const dataFont = new CanvasKit.Font(roboto, 20);
function drawFrame(canvas) {
let height = 40;
canvas.drawText(flagsText, 100, height, textPaint, textFont);
height += 80;
const flags = CanvasKit.CodeUnits.compute(flagsText);
let result = '0: ';
for (let i = 0; i < flags.length; ++i) {
const flag = flags[i].flags;
if (flagsText[i] === '|') {
canvas.drawText(result, 100, height, dataPaint, dataFont);
height += 40;
result = '' + (i + 1) + ': ';
} else if (flag === 0) {
result += flagsText[i];
} else {
result += '{';
result += (flag & CanvasKit.CodeUnitFlags.Ideographic.value) !== 0 ? 'I' : '';
result += (flag & CanvasKit.CodeUnitFlags.Whitespace.value) !== 0 ? 'S' : '';
result += (flag & CanvasKit.CodeUnitFlags.Space.value) !== 0 ? 'W' : '';
result += (flag & CanvasKit.CodeUnitFlags.Control.value) !== 0 ? 'C' : '';
result += '}';
}
}
}
surface.requestAnimationFrame(drawFrame);
return surface;
}
</script>

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

# CanvasKit Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Breaking
- `Path` objects are now immutable.
- `PathBuilder` has been exposed to allow clients to create `Path` objects incrementally.
### Fixed
- Fix compilation setting to keep up with [emsdk#24079](https://github.com/emscripten-core/emscripten/pull/24079)
## [0.40.0] - 2025-03-31
### Changed
- `Typeface.MakeFreeTypeFaceFromData` is now `Typeface.MakeTypefaceFromData` to be consistent
with the rest of the Skia library in the capitalization of the f in Typeface.
(CK still uses Freetype under the hood).
- Passing a `null` `Typeface` to the `Font` constructor no longer uses the default typeface. See
`CanvasKit.Typeface.GetDefault()` as a way to get the compiled-in typeface to replace this
behavior.
- `MakeManagedAnimation` no longer falls back to the built-in typeface if the provided
FreeType data is not in the assets map.
### Added
- `CanvasKit.Typeface.GetDefault()` as a way to explicitly get the compiled-in typeface (if any).
- `Canvas.quickReject` to quickly check if a Rect is within the current clip region.
- `Canvas.saveLayer` now accepts a `TileMode` argument which affects the backdrop filter
in the save layer.
## [0.39.1] - 2023-10-12
### Fixed
- `@webgpu/types` is actually a dependency, not just a devDependency.
## [0.39.0] - 2023-10-11
### Added
- `ImageFilter.getOutputBounds` returns the adjusted bounds of a rect after
applying the `ImageFilter`.
- `Picture.cullRect` which gives approximate bounds of the draw commands in the
picture.
- `Picture.approximateBytesUsed` which returns an approximation of the bytes
used to store this picture. This size does not include large objects like
images.
- `FontMgr.matchFamilyStyle` finds the closest matching typeface to the specified familyName and style.
- `Paint.setBlender` Sets the current blender.
- `Blender.Mode` Create a blender that implements the specified BlendMode.
- `RuntimeEffect.MakeForBlender` Compiles a RuntimeEffect from the given blender code.
- `ManagedAnimation` getters and setters for lottie slots exported by Essential Graphics in AE.
Color, scalar, vec2, text, and image slot types are supported.
- `ManagedAnimation` WYSIWYG editor API: `attachEditor`, `enableEditor`, `dispatchEditorKey`,
`dispatchEditorPointer`.
- `InputState` and `ModifierKey` enums.
- `Paragraph.getClosestGlyphInfoAtCoordinate` and `Paragraph.getGlyphInfoAt` return the information associated with the glyph or grapheme cluster in the paragraph at the specified location/index.
- `Paragraph.getLineMetricsAt`, returns the line metrics of a line.
- `Paragraph.getNumberOfLines`, returns the number of visible lines in the paragraph.
- `Paragraph.getLineNumberAt`, finds the line that contains the given UTF-16 index.
- `ManagedAnimation.setEditorCursorWeight` -- adjust the WYSIWYG editor cursor weight.
### Fixed
- `EmbindObject` has been updated to allow TypeScript to differentiate between opaque
types such as Shader, ColorFilter, et cetera.
### Changed
- `MakeSWCanvasSurface` now allows passing an `OffscreenCanvas` element.
- `Picture.beginRecording` takes an optional `computeBounds` boolean argument
which, when true, will cause the resulting recorded picture to compute a
more accurate `cullRect` when it is created.
## [0.38.2] - 2023-06-09
### Added
- `Paragraph.unresolvedCodepoints` which allows clients to identify gaps in font coverage
more easily.
### Fixed
- `.wasm` files are now exported in the npm package.json
## [0.38.1] - 2023-05-02
### Removed
- Particles have been removed.
### Added
- Skottie TransformValue accessors for dynamic layer transforms.
- Added `CanvasKit.FontCollection`, which wraps SkParagraph's FontCollection.
A FontCollection instance contains a cache of fonts used by SkParagraph and
a cache of paragraph layouts.
- Added `CanvasKit.ParagraphBuilder.MakeFromFontCollection` to make a
`ParagraphBuilder` that uses a given `FontCollection`.
- `Paint.setDither` is exposed.
- Documentation has been improved.
### Changed
- `Image.encodeToData` now makes use of the GPU context more consistently.
## [0.38.0] - 2023-01-12
### Changed
- `Paragraph.getRectsForRange` and `Paragraph.getRectsForPlaceholders` had been returning a list
of Float32Arrays upon which a property 'direction' had been monkey-patched (this was
undocumented). They now return an object `RectWithDirection`.
- `CanvasKit.MakeOnScreenGLSurface` allows providing a cached sample count and stencil
value to avoid repeated lookups on Surface creation.
## [0.37.2] - 2022-11-15
### Fixed
- Images made from textures correctly invalidate internal state, reducing flicker (skbug.com/40044991)
## [0.37.1] - 2022-11-08
### Fixed
- Font resolution algorithm for ellipsis in SkParagraph (skbug.com/40042867)
- GrContexts will properly target the correct WebGL context
- CanvasKit built with no_embedded_font will properly link and be able to load fonts from passed-in
bytes.
- Text styled with fontSize or heightMultiplier 0 will be invisible.
## [0.37.0] - 2022-09-07
### Added
- Paragraph has new setting: `replaceTabCharacters`.
- New API, tests and sample for SkParagraph Client provided ICU API:
- buildWithClientInfo
- getText
### Fixed
- readPixels calls could sometimes fail due to a stale internal reference to GrDirectContext.
## [0.36.1] - 2022-08-22
### Changed
- Perspective text is enabled.
### Fixed
- Text is no longer distorted on certain Adreno GPUs (http://review.skia.org/571418)
## [0.36.0] - 2022-08-16
### Added
- The following path methods: `addCircle`, `CanInterpolate`, and `MakeFromPathInterpolation`.
- The following ImageFilter factory methods: `MakeBlend`, `MakeDilate`, `MakeDisplacementMap`,
`MakeDropShadow`, `MakeDropShadowOnly`, `MakeErode`, `MakeImage`, `MakeOffset`, and `MakeShader`.
- The `MakeLuma` ColorFilter factory method.
- The `fontVariations` TextStyle property.
- `ColorFilter.MakeBlend` supports float colors under the hood and takes an optional colorspace.
### Changed
- Updated `dtslint`, `typescript`, and `@webgpu/types` versions, used for testing index.d.ts types.
### Fixed
- `Image.readPixels` should work on `Image`s created with `MakeLazyImageFromTextureSource`
(https://github.com/flutter/flutter/issues/103803)
### Known Issues
- `ImageFilter.MakeDisplacementMap` is not behaving as expected in certain circumstances.
## [0.35.0] - 2022-06-30
### Fixed
- Minor bug fixes in the TypeScript type declaration.
- Creating a Premul Image from a TextureSource should upload the texture to WebGL correctly.
### Added
- `Surface.makeImageFromTextureSource`, `Surface.updateTextureFromSource`, and
`MakeLazyImageFromTextureSource` all take an optional `srcIsPremul` to specify if their source
data has Premultiplied alpha. This avoids double multiplying alpha in certain cases.
- WebGPU support. Introduced `CanvasKit.MakeGPUDeviceContext`, `CanvasKit.MakeGPUCanvasContext`,
`CanvasKit.MakeGPUCanvasSurface`, and `CanvasKit.MakeGPUTextureSurface` which are compatible with
WebGPU `GPUDevice` and `GPUTexture` objects.
- Typescript definitions for WebGPU API functions that are compatible with `@webgpu/types`
(https://www.npmjs.com/package/@webgpu/types).
- `CanvasKit.MakeCanvasSurface` is now deprecated. Clients should specify a backend target
explicitly using `CanvasKit.MakeSWCanvasSurface`, `CanvasKit.MakeOnScreenGLSurface`,
`CanvasKit.MakeGPUCanvasSurface`, and `CanvasKit.MakeGPUTextureSurface`.
- `CanvasKit.MakeGrContext` is now deprecated. Clients should use `CanvasKit.MakeWebGLContext` and
`CanvasKit.MakeGPUDeviceContext` instead.
## [0.34.1] - 2022-06-02
### Added
- `Canvas.getDeviceClipBounds` (skbug.com/40044431)
### Fixed
- `RuntimeEffect.makeShader` and `RuntimeEffect.makeShaderWithChildren` can properly accept
uniform data as MallocObj or derived TypedArrays without incorrectly freeing the uniform data.
## [0.34.0] - 2022-05-05
### Breaking
- `SkRuntimeEffect.makeShader` and `SkRuntimeEffect.makeShaderWithChildren` no longer accept
an `isOpaque` parameter. These functions will now make a best effort to determine if your
shader always produces opaque output, and optimize accordingly. If you definitely want your
shader to produce opaque output, do so in the shader's SkSL code.
### Added
- `SkPicture.makeShader`
- Skia now has a GN toolchain that is used to compile CanvasKit. Ideally, all settings should
be the same, but there may be some subtle differences in practice. This changes the setup
to build CanvasKit (users no longer need to download emsdk themselves).
### Changed
- If an invalid matrix type is passed in (e.g. not an array, TypedArray, or DOMMatrix), CanvasKit
will throw instead of drawing incorrectly.
### Fixed
- SkParagraph objects no longer have their glyphs garbled when stored to an SkPicture.
(skbug.com/40044329)
## [0.33.0] - 2022-02-03
### Added
- `Surface.updateTextureFromSource` prevents flickering on some platforms by re-using the texture
for a given `Image` instead of needing to always create a new one via
`Surface.makeImageFromTextureSource`. (skbug.com/40043812)
- `ParagraphBuilder.reset` allows re-use of the underlying memory.
- `PathEffect.MakePath2D`, `PathEffect.MakePath1D` and `PathEffect.MakeLine2D`.
### Changed
- Surface factories always produce a surface with an attached color space. Specifying `null` to
`CanvasKit.MakeWebGLCanvasSurface` or calling any factory that does not take a color space
will now create a surface with a color space of `CanvasKit.ColorSpace.SRGB`.
- We now build/ship with emscripten 3.1.3.
- Internal calls no longer use dynamic dispatch (skbug.com/40043887).
- JPEG and WEBP encoding are turned on by default in full version (in /bin/full/).
### Fixed
- Supplying textures via `Surface.makeImageFromTextureSource` should not cause issues with
Mipmaps or other places where Skia needs to create textures (skbug.com/40043889)
- `CanvasKit.MakeRenderTarget` correctly takes 2 or 3 params, as per the documentation.
- `CanvasKit.MakeOnScreenGLSurface` and other gpu surface constructors correctly adjust the
underlying WebGL context, avoiding corruption and mismatched textures
(https://github.com/flutter/flutter/issues/95259).
## [0.32.0] - 2021-12-15
### Breaking
- `Canvas.drawVertices` and `Canvas.drawPatch` treat the default blend mode differently.
See https://bugs.chromium.org/p/skia/issues/detail?id=12662.
- `Canvas.markCTM` and `Canvas.findMarkedCTM` have been removed. They were effectively no-ops.
### Added
- Rough implementation of `measureText` to Canvas2D emulation layer. For accurate numbers, clients
should use a real shaping library, like SkParagraph.
- `AnimatedImage.currentFrameDuration` has been added, as well as some clarifying documentation.
### Fixed
- Drawing images created from MakeLazyImageFromTextureSource should no longer cause a draw to only
partially show up on some frames <skbug.com/40043831>.
## [0.31.0] - 2021-11-16
### Added
- `CanvasKit.MakeLazyImageFromTextureSource`, which is similar to
`Surface.makeImageFromTextureSource`, but can be re-used across different WebGL contexts.
### Breaking
- `Surface.makeImageFromTextureSource` now takes an optional ImageInfo or PartialImageInfo
instead of optional width and height. Sensible defaults will be used if not supplied.
### Fixed
- Some `Surface` methods would not properly switch to the right WebGL context.
- Warnings about `INVALID_ENUM: enable: invalid capability` should be reduced/eliminated.
### Removed
- `FontMgr.MakeTypefaceFromData` and `FontMgr.RefDefault` have been removed in favor of
`Typeface.MakeFreeTypeFaceFromData`
### Changed
- `make release`, `make debug`, and variants put the output in a different location (./build).
- Example .html files load CanvasKit from the new location (./build).
### Type Changes (index.d.ts)
- `Surface.requestAnimationFrame` and `Surface.drawOnce` are properly documented.
- Fixed typo in TextStyle (decrationStyle => decorationStyle)
## [0.30.0] - 2021-09-15
### Removed
- `Surface.grContext` and `Surface.openGLversion` - these had been undocumented and are no longer
exposed.
- `CanvasKit.setCurrentContext` and `CanvasKit.currentContext`. Existing calls can be deleted.
### Changed
- CanvasKit APIs now handle switching between WebGL contexts automatically.
- Reduced overhead when switching between WebGL contexts.
### Type Changes (index.d.ts)
- `Canvas.drawImage*` calls are correctly documented as accepting an optional `Paint` or null.
## [0.29.0] - 2021-08-06
### Added
- `Path.makeAsWinding` has been added to convert paths with an EvenOdd FillType to the
equivalent area using the Winding FillType.
### Breaking
- `Paint.getBlendMode()` has been removed.
- `Canvas.drawImageAtCurrentFrame()` has been removed.
- FilterQuality enum removed -- pass `FilterOptions` | `CubicResampler` instead.
### Type Changes (index.d.ts)
- Replaced all `object` with actual types, including `AnimationMarker`.
## [0.28.1] - 2021-06-28
### Added
- `Typeface.MakeFreeTypeFaceFromData` as a more convenient way to create a Typeface from the bytes
of a .ttf, .woff, or .woff2 file.
- `Typeface.getGlyphIDs` - provides the same functionality as `Font.getGlyphIDs`.
### Changed
- ICU has been updated from v65 to v69.
- Freetype has been updated from f9350be to ff40776.
### Fixed
- We should no longer have to decode the same font multiple times (skbug.com/40043207)
- `Font.getGlyphIDs` had the wrong type for the third argument. It is now correctly a Uint16Array.
### Deprecated
- `FontMgr.MakeTypefaceFromData` will be removed in favor of `Typeface.MakeFreeTypeFaceFromData`
- `FontMgr.RefDefault` will be removed in an upcoming version. It's only real use was
for `FontMgr.MakeTypefaceFromData`.
## [0.28.0] - 2021-06-17
### Added
- `Surface.makeImageFromTexture` and `Surface.makeImageFromTextureSource` as easy ways to provide
CanvasKit with a WebGL texture and interact with WebGL texture sources (e.g. &lt;video&gt;)
### Changed
- We now build/ship with emscripten 2.0.20.
### Breaking
- `Path.toCmds()` returns a flattened Float32Array instead of a 2D Array.
- `Canvaskit.Path.MakeFromCmds` no longer accepts a 2D Array. Inputs must be flattened,
but can be an array, a TypedArray, or a MallocObj.
- `CanvasKit.*Builder` have all been removed. Clients should use Malloc instead.
### Removed
- `CanvasKit.Shader.MakeLerp`, the same effect can be easily generated with `RuntimeEffect`
### Known Bugs
- On legacy (non-ANGLE) SwiftShader, certain paths that require tessellation may not be drawn
correctly when using a WebGL-backed surface. (skbug.com/40043054)
## [0.27.0] - 2021-05-20
### Added
- `Font.getGlyphIntercepts()`
### Fixed
- Bug with images using certain exif metadata. (skbug.com/40043056)
### Removed
- `Canvas.flush`, which had been previously deprecated. `Surface.flush` is the preferred method.
- `AnimatedImage.getCurrentFrame`, which had been previously deprecated.
`AnimatedImage.makeImageAtCurrentFrame` is the replacement, which behaves exactly the same.
## [0.26.0] - 2021-04-23
### Added
- Add 'isEmbolden, setEmbolden' to 'Font'
- Add 'drawGlyphs' to 'Canvas'
- Add `drawPatch` to `Canvas`.
- Add `Strut` as a `RectHeightStyle` enum.
- `CanvasKit.RuntimeEffect` now supports integer uniforms in the SkSL. These are still passed
to `RuntimeEffect.makeShader` as floats (like all other uniforms), and will be converted to
integers internally, to match the expectations of the shader.
- Add 'halfLeading' to `TextStyle` and `StrutStyle`.
- `ParagraphStyle` now accepts textHeightBehavior.
### Removed
- `Picture.saveAsFile()`, in favor of `Picture.serialize()` where clients can control how to
store/encode the bytes.
## [0.25.1] - 2021-03-30
### Added
- Skottie accessors for dynamic text properties (text string, font size).
- Optional sampling parameter to drawAtlas (paint filter-quality is ignored/deprecated)
### Fixed
- Fonts should not be leaked https://bugs.chromium.org/p/skia/issues/detail?id=11778
## [0.25.0] - 2021-03-02
### Added
- A full build of CanvasKit is now in /bin/full.
- `CanvasKit.rt_effect` to test if the RuntimeEffect code was compiled in.
### Breaking
- The `ShapedText` type has been removed. Clients who want ShapedText should use the
Paragraph APIs.
### Removed
- `Font.measureText`, which had been previously deprecated. Clients should use either
Paragraph APIs or `Font.getGlyphWidths` instead (the latter does no shaping).
- `Font.getWidths`, which had been previously deprecated. Clients should use `Font.getGlyphWidths`.
### Type Changes (index.d.ts)
- Documentation added for `managed_skottie`, `particles`, and `skottie` feature constants.
## [0.24.0] - 2021-02-18
### Added
- The Skottie factory (MakeManagedAnimation) now accepts an optional logger object.
### Breaking
- `CanvasKit.getDataBytes` has been removed, as has the Data type. The 2 APIS that returned
Data now return Uint8Array containing the bytes directly. These are `Image.encodeToData`
(now named `Image.encodeToBytes`) and `SkPicture.serialize`. These APIs return null if
the encoding or serialization failed.
### Type Changes (index.d.ts)
- `Image.encodeToDataWithFormat` was incorrectly documented as its own thing.
## [0.23.0] - 2021-02-04
### Added
- Constants for the shadow flags. Of note, some of these values can be used on previous releases.
- `getShadowLocalBounds()` to estimate the bounds of the shadows drawn by `Canvas.drawShadow`.
- compile.sh now takes "no_matrix", which will omit the helper JS to deal with 3x3, 4x4 and
SkColorMatrix (in case clients have logic to deal with that themselves).
- `CanvasKit.RuntimeEffect.Make` now takes an optional callback function that will be called
with any compilation error.
- `CanvasKit.RuntimeEffect` now exposes uniforms. The number, dimensions, and name of each
uniform can be queried, using `RuntimeEffect.getUniformCount`, `RuntimeEffect.getUniform`, and
`RuntimeEffect.getUniformName`. The total number of floats across all uniforms (that must be
passed to `RuntimeEffect.makeShader`) can be queried with `RuntimeEffect.getUniformFloatCount`.
### Breaking
- `MakeImprovedNoise` is removed.
- Particles now use a single code string containing both Effect and Particle code. Uniform APIs are
now shared between Effect and Particle programs, and are no longer prefixed with `Effect` or
`Particle`. For example, instead of `ParticleEffect.getEffectUniform` and
`ParticleEffect.getParticleUniform`, there is now just: `ParticleEffect.getUniform`.
### Changed
- `Path.getPoint()` and `SkottieAnimation.size()` now return a TypedArray instead of a normal
array. Additionally, they take an optional parameter to allow the result to be copied into
that provided TypedArray instead of a new one being allocated.
- APIs that passed in points should have less overhead (and now can accept a TypedArray).
- `Canvas.drawShadow()` now accepts zPlaneParams and lightPos as Malloc'ed and regular
Float32Arrays. `getShadowLocalBounds()` does as well.
- `ContourMeasure.getPosTan` returns a Float32Array instead of a normal array. Additionally,
this method takes an optional parameter to allow the result to be copied into
that provided Float32Array instead of a new one being allocated.
### Fixed
- Improper error returned when a WebGL context could not be used.
- 4x4 matrices are "downsampled" properly if necessary to 3x3 matrices by removing the third
column and the third row.
- `SkottieAnimation.size()` was incorrectly returning an object. It now returns a TypedArray of
length 2 (w, h).
### Deprecated
- `Canvas.drawImageRect`, `Canvas.drawImage`, `Canvas.drawAtlas`,
These rely on the Paint's FilterQuality, which is going away. Pass sampling options explicitly.
### Removed
- `PathMeasure`, which was deprecated and replaced with `ContourMeasure`.
## [0.22.0] - 2020-12-17
### Added
- `Canvas.drawImageCubic`, `Canvas.drawImageOptions`, `Canvas.drawImageRectCubic`,
`Canvas.drawImageRectOptions` to replace functionality that previously required FilterQuality.
- A copy of this changelog is published in NPM releases for easier discovery.
### Breaking
- `Canvas.drawImageNine` now takes a required FilterMode (the Paint still is optional).
## [0.21.0] - 2020-12-16
### Added
- `getImageInfo()` and `getColorSpace()` to the `Image` type.
- `CanvasKit.deleteContext()` for deleting WebGL contexts when done with them, resizing, etc.
- `Image.makeCopyWithDefaultMipmaps()` for use with `Image.makeShaderOptions`; necessary if
choosing a `MipmapMode` that is not `None`.
### Breaking
- `Path.addPoly()` no longer accepts a 2d array of points, but a flattened 1d array.
- `MakeVertices()` no longer accepts 2d arrays of points or texture coordinates, but
flattened 1d arrays in both places.
- `Paint.setFilterQuality`, `Paint.getFilterQuality`, `Image.makeShader` have been removed.
The new way to specify interpolation settings is with the newly added `Image.makeShader*`
methods. `Image.makeShaderCubic` is a replacement for high quality; `Image.makeShaderOptions`
is for medium/low.
### Changed
- `MakeImage` is now documented in the Typescript types (index.d.ts). The parameters have been
streamlined to align with other, similar APIs.
- `MakeAnimatedImageFromEncoded` respects Exif metadata. `MakeImageFromEncoded` already did so
(and continues to do so).
- The Canvas2D emulation layer always uses high quality image smoothing (this drastically
simplifies the underlying code).
- We now compile CanvasKit with emsdk 2.0.10 when testing and deploying to npm.
- Instead of shipping a "core" build to npm, we ship a "profiling" build, which is the same as
the main build, just with unmangled function calls and other debugging info useful for
determining where runtime is spent.
### Fixed
- `Canvas.drawPoints` correctly takes a flattened Array or TypedArray of points (as the
documentation says), not a 2D array.
### Type Changes (index.d.ts)
- Documented additional type for InputFlexibleColorArray.
## [0.20.0] - 2020-11-12
### Added
- `MakeFractalNoise`, `MakeImprovedNoise`, and `MakeTurbulence` have been added to
`CanvasKit.Shader`.
- `MakeRasterDirectSurface` for giving the user direct access to drawn pixels.
- `getLineMetrics` to Paragraph.
- `Canvas.saveLayerPaint` as an experimental, undocumented "fast path" if one only needs to pass
the paint.
- Support for .woff and .woff2 fonts. Disable .woff2 for reduced code size by supplying
no_woff2 to compile.sh. (This removes the code to do brotli decompression).
### Breaking
- `CanvasKit.MakePathFromSVGString` was renamed to `CanvasKit.Path.MakeFromSVGString`
- `CanvasKit.MakePathFromOp` was renamed to `CanvasKit.Path.MakeFromOp`
- The API for `Canvas.readPixels` and `Image.readPixels` has been reworked to more accurately
reflect the C++ backend and each other. bytesPerRow is now a required parameter. They take an
ImageInfo object to specify the output format. Additionally they take an optional malloc'd
object as the last parameter. If provided, the data will be copied into there instead of
allocating a new buffer.
### Changed
- We now compile CanvasKit with emsdk 2.0.6 when testing and deploying to npm.
- We no longer compile with rtti on, saving about 1% in code size.
- `CanvasKit.Shader.Blend`, `...Color`, and `...Lerp` have been renamed to
`CanvasKit.Shader.MakeBlend`, `...MakeColor` and `...MakeLerp` to align with naming conventions.
The old names will be removed in an upcoming release.
### Removed
- `CanvasKit.MakePathFromCmds`; Was deprecated in favor of `CanvasKit.Path.MakeFromCmds`.
- `new CanvasKit.Path(path)` in favor of existing `path.copy()`.
- Unused internal APIs (_getRasterN32PremulSurface, Drawable)
- `measureText` from the CanvasContext2D emulation layer due to deprecation of measureText.
### Deprecated
- `Font.getWidths` in favor of `Font.getGlyphIDs` and `Font.getGlyphWidths`.
- `Font.measureText` in favor of the Paragraph APIs (which actually do shaping).
### Type Changes (index.d.ts)
- Return value for MakeFromCmds correctly reflects the possibility of null.
- `CanvasKit.GrContext` was renamed to `CanvasKit.GrDirectContext`.
- Add docs/types for Shader Gradients (e.g. `CanvasKit.Shader.MakeLinearGradient`).
## [0.19.0] - 2020-10-08
### Breaking
- "Sk" has been removed from all names. e.g. `new CanvasKit.SkPaint()` becomes
`new CanvasKit.Paint()`. See `./types/index.d.ts` for all the new names.
### Removed
- `Surface.captureFrameAsSkPicture`; it was deprecated previously.
- `CanvasKit.MakeSkCornerPathEffect`, `CanvasKit.MakeSkDiscretePathEffect`,
`CanvasKit.MakeBlurMaskFilter`, `CanvasKit.MakeSkDashPathEffect`,
`CanvasKit.MakeLinearGradientShader`, `CanvasKit.MakeRadialGradientShader`,
`CanvasKit.MakeTwoPointConicalGradientShader`; these were deprecated previously and have
replacements like `CanvasKit.PathEffect.MakeDash`.
- `Canvas.concat44`; it was deprecated previously, just use `Canvas.concat`
## [0.18.1] - 2020-10-06
### Added
- Typescript types (and documentation) are now in the types subfolder. We will keep these updated
as we make changes to the CanvasKit library.
## [0.18.0] - 2020-10-05
### Breaking
- SkRect are no longer returned from `CanvasKit.LTRBRect`, `CanvasKit.XYWHRect` nor
are accepted as JS objects. Instead, the format is 4 floats in either an array, a
Float32Array or a piece of memory returned by CanvasKit.Malloc. These floats are the
left, top, right, bottom numbers of the rectangle.
- SkIRect (Rectangles with Integer values) are no longer accepted as JS objects.
Instead, the format is 4 ints in either an array, an Int32Array or a piece of memory
returned by CanvasKit.Malloc. These ints are the left, top, right, bottom numbers of
the rectangle.
- SkRRect (Rectangles with rounded corners) are no longer returned from `CanvasKit.RRectXY`
nor are accepted as JS objects. Instead, the format is 12 floats in either an array, a
Float32Array or a piece of memory returned by CanvasKit.Malloc. The first 4 floats
are the left, top, right, bottom numbers of the rectangle and then 4 sets of points
starting in the upper left corner and going clockwise. This change allows for faster
transfer between JS and WASM code.
- `SkPath.addRoundRect` has been replaced with `SkPath.addRRect`. The same functionality
can be had with the `CanvasKit.RRectXY` helper.
- `SkPath.addRect` no longer accepts 4 floats as separate arguments. It only accepts
an SkRect (an array/Float32Array of 4 floats) and an optional boolean for
determining clockwise or counter-clockwise directionality.
- The order of `SkCanvas.saveLayer` arguments is slightly different (more consistent).
It is now `paint, bounds, backdrop, flags`
### Changed
- We now compile CanvasKit with emsdk 2.0.0 when testing and deploying to npm.
- WebGL interface creation is a little leaner in terms of code size and speed.
- The signature of `main` used with SkSL passed to `CanvasKit.SkRuntimeEffect.Make` has changed.
There is no longer an `inout half4 color` parameter, effects must return their color instead.
Valid signatures are now `half4 main()` or `half4 main(float2 coord)`.
- `SkPath.getBounds`, `SkShapedText.getBounds`, and `SkVertices.bounds` now
take an optional argument. If a Float32Array with length 4 or greater is
provided, the bounds will be copied into this array instead of allocating
a new one.
- `SkCanvas.drawAnimatedImage` has been removed in favor of calling
`SkCanvas.drawImageAtCurrentFrame` or `SkAnimatedImage.makeImageAtCurrentFrame` and then
`SkCanvas.drawImage`.
- `SkTextBlob.MakeFromRSXform` also accepts a (possibly Malloc'd) Float32Array of RSXforms (
see SkRSXform for more.)
### Removed
- `SkCanvas.drawRoundRect` has been removed in favor of `SkCanvas.drawRRect`
The same functionality can be had with the `CanvasKit.RRectXY` helper.
- `SkPath.arcTo` which had been deprecated in favor of `SkPath.arcToOval`,
`SkPath.arcToRotated`, `SkPath.arcToTangent`.
- Extraneous ColorTypes from `ColorType` enum.
### Added
- `CanvasKit.LTRBiRect` and `CanvasKit.XYWHiRect` as helpers to create SkIRects.
- `SkCanvas.drawRect4f` as a somewhat experimental way to have array-free APIs for clients that
already have their own representation of Rect. This is experimental because we don't know
if it's faster/better under real-world use and because we don't want to commit to having these
for all Rect APIs (and for similar types) until it has baked in a bit.
- Added the following to `TextStyle`:
- `decorationStyle`
- `textBaseline`
- `letterSpacing`
- `wordSpacing`
- `heightMultiplier`
- `locale`
- `shadows`
- `fontFeatures`
- Added `strutStyle` to `ParagraphStyle`.
- Added `addPlaceholder` to `ParagraphBuilder`.
- Added `getRectsForPlaceholders` to `Paragraph`.
- `SkFont.getGlyphIDs`, `SkFont.getGlyphBounds`, `SkFont.getGlyphWidths` for turning code points
into GlyphIDs and getting the associated metrics with those glyphs. Note: glyph ids are only
valid for the font of which they were requested.
- `SkTextBlob.MakeFromRSXformGlyphs` and `SkTextBlob.MakeFromGlyphs` as a way to build TextBlobs
using GlyphIDs instead of code points.
- `CanvasKit.MallocGlyphIDs` as a helper for pre-allocating space on the WASM heap for Glyph IDs.
### Deprecated
- `SkAnimatedImage.getCurrentFrame`; prefer `SkAnimatedImage.makeImageAtCurrentFrame` (which
follows the establishing naming convention).
- `SkSurface.captureFrameAsSkPicture` will be removed in a future release. Callers can simply
use `SkPictureRecorder` directly.
- `CanvasKit.FourFloatArrayHelper` and related helpers (mostly helping with drawAtlas).
`CanvasKit.Malloc` is the better tool and will replace these soon.
- `SkPathMeasure`; SkContourMeasureIter has all the same functionality and a cleaner pattern.
### Fixed
- Addressed Memory leak in `SkCanvas.drawText`.
- Made SkTextBlob hang on to less memory during its lifetime.
- `SkPath.computeTightBounds()` works again. Like getBounds() it takes an optional argument
to put the bounds into.
## [0.17.3] - 2020-08-05
### Added
- Added `CanvasKit.TypefaceFontProvider`, which can be used to register fonts
with a font family alias. For example, "Roboto Light" may be registered with
the alias "Roboto", and it will be used when "Roboto" is used with a light
font weight.
- Added `CanvasKit.ParagraphBuilder.MakeFromFontProvider` to make a
`ParagraphBuilder` from a `TypefaceFontProvider`.
- Added `CanvasKit.ParagraphBuilder.pushPaintStyle` which can be used to stroke or fill
text with paints instead of simple colors.
## [0.17.2] - 2020-07-22
### Fixed
- Shader programs are no longer generated with `do-while` loops in WebGL 1.0.
## [0.17.1] - 2020-07-21
### Added
- Compile option to deserialize effects in skps `include_effects_deserialization`.
### Changed
- Pathops and SKP deserialization/serialization enabled on the npm build.
## [0.17.0] - 2020-07-20
### Added
- Added `CanvasKit.MakeImageFromCanvasImageSource` which takes either an HTMLImageElement,
SVGImageElement, HTMLVideoElement, HTMLCanvasElement, ImageBitmap, or OffscreenCanvas and returns
an SkImage. This function is an alternative to `CanvasKit.MakeImageFromEncoded` for creating
SkImages when loading and decoding images. In the future, codesize of CanvasKit may be able to be
reduced by removing image codecs in wasm, if browser APIs for decoding images are used along with
`CanvasKit.MakeImageFromCanvasImageSource` instead of `CanvasKit.MakeImageFromEncoded`.
- Three usage examples of `CanvasKit.MakeImageFromCanvasImageSource` in core.spec.ts.
- Added support for asynchronous callbacks in perfs and tests.
- `CanvasKit.SkPath.MakeFromVerbsPointsWeights` and `CanvasKit.SkPath.addVerbsPointsWeights` for
supplying many path operations (e.g. moveTo, cubicTo) at once.
- The object returned by `CanvasKit.malloc` now has a `subarray` method which works exactly like
the normal TypedArray version. The TypedArray which it returns is also backed by WASM memory
and when passed into CanvasKit will be used w/o copying the data (just like
`Malloc.toTypedArray`).
- `SkM44.setupCamera` to return a 4x4 matrix which sets up a perspective view from a camera.
- `SkPath.arcToOval`, `SkPath.arcToTangent`, and `SkPath.arcToRotated` to replace the three
overloads of `SkPath.arcTo`. https://github.com/flutter/flutter/issues/61305
### Changed
- In all places where color arrays are accepted (gradient makers, drawAtlas, and MakeSkVertices),
You can now provide either flat Float32Arrays of float colors, Uint32Arrays of int colors, or
2d Arrays of Float32Array(4) colors. The one thing you should not pass is an Array of numbers,
since canvaskit wouldn't be able to tell whether they're ints or floats without checking them all.
The fastest choice for gradients is the flat Float32Array, the fastest choice for drawAtlas and
MakeSkVertices is the flat Uint32Array.
- Color arrays may also be objects created with CanvasKit.Malloc
- renamed `reportBackendType` to `reportBackendTypeIsGPU` and made it return a boolean
- `MakeWebGLCanvasSurface` can now accept an optional dictionary of WebGL context attributes that
can be used to override default attributes.
### Fixed
- `TextStyle.color` can correctly be a Malloc'd Float32Array.
- Support wombat-dressing-room. go/npm-publish
### Deprecated
- `CanvasKit.MakePathFromCmds` has been renamed to `CanvasKit.SkPath.MakeFromCmds`. The alias
will be removed in an upcoming release.
- `SkPath.arcTo` Separated into three functions.
## [0.16.2] - 2020-06-05
### Fixed
- A bug where loading fonts (and other memory intensive calls) would cause CanvasKit
to infrequently crash with
`TypeError: Cannot perform %TypedArray%.prototype.set on a neutered ArrayBuffer`.
- Incorrectly freeing Malloced colors passed into computeTonalColors.
## [0.16.1] - 2020-06-04
### Fixed
- Colors are unsigned to be compatible with Flutter Web and previous behavior, not
signed ints.
## [0.16.0] - 2020-06-03
### Added
- Support for wide-gamut color spaces DisplayP3 and AdobeRGB. However, correct representation on a
WCG monitor requires that the browser is rendering everything to the DisplayP3 or AdobeRGB
profile, since there is not yet any way to indicate to the browser that a canvas element has a
non-sRGB color space. See color support example in extra.html. Only supported for WebGL2 backed
surfaces.
- Added `SkSurface.reportBackendType` which returns either 'CPU' or 'GPU'.
- Added `SkSurface.imageInfo` which returns an ImageInfo object describing the size and color
properties of the surface. colorSpace is added to ImageInfo everywhere it is used.
- `CanvasKit.Free` to explicitly clean up memory after `CanvasKit.Malloc`. All memory allocated
with `CanvasKit.Malloc` must be released with `CanvasKit.Free` or it will be leaked. This can
improve performance by reducing the copying of data between the JS and WASM side.
- `CanvasKit.ColorAsInt`, `SkPaint.setColorComponents`, `SkPaint.setColorInt`,
`SkCanvas.drawColorComponents`, `SkCanvas.drawColorInt` for when clients want
to avoid the overhead of allocating an array for color components and only need 8888 color.
### Changed
- We now compile/ship with Emscripten v1.39.16.
- `CanvasKit.MakeCanvasSurface` accepts a new enum specifying one of the three color space and
pixel format combinations supported by CanvasKit.
- all `_Make*Shader` functions now accept a color space argument at the end. leaving it off or
passing null makes it behave as it did before, defaulting to sRGB
- `SkPaint.setColor` accepts a new color space argument, defaulting to sRGB.
- Fewer allocations required to send Color and Matrices between JS and WASM layer.
- All APIs that take a 1 dimensional array should also accept the object returned by Malloc. It is
recommended to pass the Malloc object, as the TypedArray could be invalidated any time
CanvasKit needs to allocate memory and needs to resize to accommodate.
### Breaking
- `CanvasKitInit(...)` now directly returns a Promise. As such, `CanvasKitInit(...).ready()`
has been removed.
- `CanvasKit.MakeCanvasSurface` no longer accepts width/height arguments to override those on
the canvas element. Use the canvas element's width/height attributes to dictate the size of
the drawing area, and use CSS width/height to set the size it will appear on the page
(it is rescaled after drawing when css sizing applies).
- Memory returned by `CanvasKit.Malloc` will no longer be automatically cleaned up. Clients
must use `CanvasKit.Free` to release the memory.
- `CanvasKit.Malloc` no longer directly returns a TypedArray, but an object that can produce
them with toTypedArray(). This is to avoid "detached ArrayBuffer" errors:
<https://github.com/emscripten-core/emscripten/issues/6747>
### Fixed
- WebGL context is no longer created with "antialias" flag. Using "antialias" caused poor AA
quality in Ganesh when trying to do coverage-based AA with MSAA unknowingly enabled. It also
reduced performance.
## [0.15.0] - 2020-05-14
### Added
- Support for DOMMatrix on all APIs that take SkMatrix (i.e. arrays or Float32Arrays of length 6/9/16).
- setEdging and setEmbeddedBitmaps to SkFont. You can disable the ability to draw aliased fonts (and save some code
size) with the compile.sh argument `no_alias_font`.
### Removed
- Previously deprecated functions `MakeSkDashPathEffect`, `MakeLinearGradientShader`,
`MakeRadialGradientShader`, `MakeTwoPointConicalGradientShader`, `MakeSkCornerPathEffect`,
`MakeSkDiscretePathEffect`
### Changed
- CanvasKit colors are now represented with a TypedArray of four floats.
- Calls to `getError` should be disabled. This may cause a performance improvement in some scenarios.
### Removed
- SkPaint.setColorf is obsolete and removed. setColor accepts a CanvasKit color which is
always composed of floats.
- localmatrix option for `SkShader.Lerp` and `SkShader.Blend`.
### Deprecated
- `SkCanvas.concat44` has been folded into concat (which now takes 3x2, 3x3, or 4x4 matrices). It will
be removed soon.
### Fixed
- Memory leak in paragraph binding code (https://github.com/flutter/flutter/issues/56938)
- Safari now properly uses WebGL1 instead of WebGL2 when WebGL2 is not available (skbug.com/40041519).
## [0.14.0] - 2020-03-18
### Added
- `SkShader.MakeSweepGradient`
- `SkCanvas.saveLayer` can now be called with 1 argument (the paint). In this case the current
effective clip will be used, as the current rect is assumed to be null.
- `SkPaint.setAlphaf`
- Clients can supply `no_codecs` to compile.sh to remove all codec encoding and decoded code.
This can save over 100 kb compressed if codecs are not needed.
### Deprecated
- `MakeSkDashPathEffect` will be removed soon. Calls can be replaced with
`SkPathEffect.MakeDash`.
- `MakeLinearGradientShader` will be removed soon. Calls can be replaced with
`SkShader.MakeLinearGradient`.
- `MakeRadialGradientShader` will be removed soon. Calls can be replaced with
`SkShader.MakeRadialGradient`.
- `MakeTwoPointConicalGradientShader` will be removed soon. Calls can be replaced with
`SkShader.MakeTwoPointConicalGradient`.
### Fixed
- Shadows are properly draw on fillRect and strokeRect in the canvas2d emulation layer.
- Shadow offsets properly ignore the CTM in the canvas2d emulation layer.
### Changed
- Stop compiling jpeg and webp encoders by default. This results in a 100kb binary size reduction.
Clients that need these encoders can supply `force_encode_webp` or `force_encode_jpeg` to
compile.sh.
### Removed
- Removed inverse filltypes.
- Removed StrokeAndFill paint style.
- Removed TextEncoding enum (it was only used internally). All functions assume UTF-8.
## [0.13.0] - 2020-02-28
### Deprecated
- `MakeSkCornerPathEffect` will be removed soon. Calls can be replaced with
`SkPathEffect.MakeCorner`.
- `MakeSkDiscretePathEffect` will be removed soon. Calls can be replaced with
`SkPathEffect.MakeDiscrete`.
### Added
- `SkSurface.drawOnce` for drawing a single frame (in addition to already existing
`SkSurface.requestAnimationFrame` for animation logic).
- `CanvasKit.parseColorString` which processes color strings like "#2288FF"
- Particles module now exposes effect uniforms, which can be modified for live-updating.
- Experimental 4x4 matrices added in `SkM44`.
- Vector math functions added in `SkVector`.
- `SkRuntimeEffect.makeShaderWithChildren`, which can take in other shaders as fragmentProcessors.
- `GrContext.releaseResourcesAndAbandonContext` to free up WebGL contexts.
- A few methods on `SkFont`: `setHinting`, `setLinearMetrics`, `setSubpixel`.
### Changed
- We now compile/ship with Emscripten v1.39.6.
- `SkMatrix.multiply` can now accept any number of matrix arguments, multiplying them
left-to-right.
- SkMatrix.invert now returns null when the matrix is not invertible. Previously it would return an
identity matrix. Callers must determine what behavior would be appropriate in this situation.
- In Canvas2D compatibility layer, the underlying SkFont will have setSubpixel(true).
- Bones are removed from Vertices builder
### Fixed
- Support for .otf fonts (.woff and .woff2 still not supported).
## [0.12.0] - 2020-01-22
### Added
- `SkFontMgr.countFamilies` and `SkFontMgr.getFamilyName` to expose the parsed font names.
### Changed
- SKP serialization/deserialization now available (can be disabled with the 'no_skp').
`SkPicture.DEBUGONLY_saveAsFile` renamed to `SkPicture.saveAsFile` and
`CanvasKit.MakeSkPicture` is now exposed. SKP support is not shipped to npm builds.
`force_serialize_skp` has been removed since it opt-out, not opt-in.
### Fixed
- Bug that sometimes resulted in 'Cannot perform Construct on a neutered ArrayBuffer'
- Bug with SkImage.readPixels (skbug.com/40041118)
- Bug with transparent colors in Canvas2d mode (skbug.com/40041129)
## [0.11.0] - 2020-01-10
### Added
- A "Core" build that removes Fonts, the Skottie animation player, the Particles demo,
and PathOps is available in `bin/core/`. It is about half the size of the "CoreWithFonts"
build.
- Experimental Runtime shader available for custom builds.
- WebP support.
- `SkAnimatedImage.getCurrentFrame` which returns an SkImage.
### Fixed
- `CanvasKit.SaveLayerInitWithPrevious` and `CanvasKit.SaveLayerF16ColorType` constants.
- Some compilation configurations, for example, those with no fonts or just one of particles/skottie.
### Changed
- Small tweaks to compilation settings to reduce code size and linkage time.
- JS functions are no longer provided when the underlying c++ calls have been compiled out.
### Removed
- `SkShader.Empty`
- Support for Type 1 Fonts. These are ancient and removing them saves about 135k
of code size.
### Breaking
- In an effort to reduce code size for most clients, npm now contains two CanvasKit builds.
In `bin/` there is the "CoreWithFonts" build that contains most functionality from 0.10.0.
However, we no longer ship the Skottie animation player, nor the Particles demo. Further,
PathOps are removed from this build `MakePathFromOp`, `SkPath.op` and `SkPath.simplify`.
Clients who need any of those features are encouraged to create a custom build using
`compile.sh`.
- `SkPicture.DEBUGONLY_saveAsFile` was accidentally included in release builds. It has been
removed. Clients who need this in a release build (e.g. to file a bug report that only
reproduces in release) should do a custom build with the `force_serialize_skp` flag given.
### Deprecated
- `SkCanvas.drawAnimatedImage` will be renamed soon. Calls can be replaced with `SkCanvas.drawImage`
and `SkAnimatedImage.getCurrentFrame`.
## [0.10.0] - 2019-12-09
### Added
- `SkContourMeasureIter` and `SkContourMeasure` as an alternative to `SkPathMeasure`.
- CanvasKit image decode cache helpers: getDecodeCacheLimitBytes(), setDecodeCacheLimitBytes(),
and getDecodeCacheUsedBytes().
- `SkShader.Blend`, `SkShader.Color`, `SkShader.Empty`, `SkShader.Lerp`.
### Changed
- The returned values from `SkParagraph.getRectsForRange` now have direction with value
`CanvasKit.TextDirection`.
### Fixed
- `MakeImage` properly in the externs file and can work with `CanvasKit.Malloc`.
## [0.9.0] - 2019-11-18
### Added
- Experimental `CanvasKit.Malloc`, which can be used to create a
TypedArray backed by the C++ WASM memory. This can save a copy in some cases
(e.g. SkColorFilter.MakeMatrix). This is an advanced feature, so use it with care.
- `SkCanvas.clipRRect`, `SkCanvas.drawColor`
- Blur, ColorFilter, Compose, MatrixTransform SkImageFilters. Can be used with `SkPaint.setImageFilter`.
- `SkCanvas.saveLayer` now takes 3 or 4 params to include up to bounds, paint, SkImageFilter, flags.
- `SkPath.rArcTo`, `SkPath.rConicTo`, `SkPath.rCubicTo`, `SkPath.rLineTo`, `SkPath.rMoveTo`,
`SkPath.rQuadTo`. Like their non-relative siblings, these are chainable.
- Add `width()`, `height()`, `reset()`, `getFrameCount()` to SkAnimatedImage.
- `SkCanvas.drawImageNine`, `SkCanvas.drawPoints` and related `PointMode` enum.
- `SkPath.addPoly`
- `SkPathMeasure.getSegment`
- More information on SkParagraph API, eg. `getLongestLine()`, `getWordBoundary`, and others.
### Deprecated
- `CanvasKit.MakeBlurMaskFilter` will be renamed/moved soon to `CanvasKit.SkMaskFilter.MakeBlur`.
### Changed
- Use newer version of Freetype2 (Tracking Skia's DEPS now).
- Use newer versions of libpng and zlib (Tracking Skia's DEPS now).
### Fixed
- null dereference when sometimes falling back to CPU.
- Actually ask WebGL for a stencil buffer.
- Can opt out of Paragraph API with no_paragraph passed into compile.sh or when using primitive_shaper.
## [0.8.0] - 2019-10-21
### Added
- `CanvasKit.MakeAnimatedImageFromEncoded`, `SkCanvas.drawAnimatedImage`.
- `CanvasKit.SkFontMgr.FromData` which takes several ArrayBuffers of font data, parses
them, reading the metadata (e.g. family names) and stores them into a SkFontMgr.
- SkParagraph as an optional set of APIs for dealing with text layout.
### Changed
- The `no_font` compile option should strip out more dead code related to fonts.
- and `no_embedded_font` option now allows creating a `SkFontMgr.FromData` instead of
always having an empty one.
- Updated to emscripten 1.38.47
- Switch to WebGL 2.0, but fall back to 1.0 when unavailable - skbug.com/40040335
### Fixed
- Null terminator bug in draw text - skbug.com/40040633
## [0.7.0] - 2019-09-18
### Added
- `SkCanvas.drawCircle()`, `SkCanvas.getSaveCount()`
- `SkPath.offset()`, `SkPath.drawOval`
- `SkRRect` support (`SkCanvas.drawRRect`, `SkCanvas.drawDRRect`, `CanvasKit.RRectXY`).
Advanced users can specify the 8 individual radii, if needed.
- `CanvasKit.computeTonalColors()`, which returns TonalColors, which has an
ambient SkColor and a spot SkColor.
- `CanvasKit.SkColorFilter` and a variety of factories. `SkPaint.setColorFilter` is the only
consumer of these at the moment.
- `CanvasKit.SkColorMatrix` with functions `.identity()`, `.scaled()`, `.concat()` and
others. Primarily for use with `CanvasKit.SkColorFilter.MakeMatrix`.
### Changed
- `MakeSkVertices` uses a builder to save a copy.
### Breaking
- When `SkPath.arcTo` is given seven arguments, it no longer turns the first four into
a `SkRect` automatically, and instead uses them as
`arcTo(rx, ry, xAxisRotate, useSmallArc, isCCW, x, y)` (see SkPath.h for more).
## [0.6.0] - 2019-05-06
### Added
- `SkSurface.grContext` now exposed. `GrContext` has new methods for monitoring/setting
the cache limits; tweaking these may lead to better performance in some cases.
`getResourceCacheLimitBytes`, `setResourceCacheLimitBytes`, `getResourceCacheUsageBytes`
- `SkCanvas.drawAtlas` for efficiently drawing multiple sprites from a sprite sheet with
a set of transforms, color blends, etc.
- `SkColorBuilder`, `RSXFormBuilder`, `SkRectBuilder` which increase performance by
reducing the amount of malloc/free calls per frame, given that the array size is fixed.
- Basic `SkPicture` support. `SkSurface.captureFrameAsSkPicture` is a helper function to
capture an `SkPicture`, which can be dumped to disk (for debugging) with
`SkPicture.DEBUGONLY_saveAsFile`.
- `SkImage.readPixels`, which returns a TypedArray of pixel values (safe to use
anywhere, doesn't need a delete()).
### Changed
- Better `GrGLCaps` support for WebGL - this shouldn't have any impacts on APIs or
correctness, except by perhaps fixing a few bugs in various surface types.
- Use unsigned ints for SkColor on the JS side - this shouldn't have any impacts
unless clients have pre-computed colors, in which case, they will need to re-compute them.
- [breaking] Moved `CanvasKit.MakeImageShader` to `SkImage.makeShader` - removed clampUnpremul
as argument.
## [0.5.1] - 2019-03-21
### Added
- `SkPathMeasure`, `RSXFormBuilder`, `SkFont.getWidths`, `SkTextBlob.MakeFromRSXform`
which were needed to add the helper function `SkTextBlob.MakeOnPath`.
- `SkSurface.requestAnimationFrame` - wrapper around window.requestAnimationFrame that
takes care of the setup/tear down required to use CanvasKit optimally. The callback
has an `SkCanvas` as the first parameter - callers should draw on that.
### Changed
- Location in Skia Git repo now `modules/canvaskit` (was `experimental/canvaskit`)
### Fixed
- Extern bug in `CanvasKit.SkMatrix.invert`
- Fallback to CPU now properly refreshes the canvas to get access to the
CanvasRenderingContext2D.
- Compile flags for better WebGL1 support for some graphics cards.
- Antialias bug on large oval paths <skbug.com/40040155>
### Deprecated
- `SkCanvas.flush` will be removed soon - client should only call `SkSurface.flush`
## [0.5.0] - 2019-03-08
### Added
- isVolitile option to `CanvasKit.MakeSkVertices`. The previous (and current default) behavior
was for this to be true; some applications may go faster if set to false.
- `SkCanvas.saveLayer(rect, paint)`
- `SkCanvas.restoreToCount(int)` which can be used with the output of .save() and .saveLayer().
- Optional particles library from modules/particles. `See CanvasKit.MakeParticles(json)`;
- More public APIs for working with Surfaces/Contexts `GetWebGLContext`,
`MakeGrContext`, `MakeOnScreenGLSurface`, `MakeRenderTarget`.
- `SkSurface.getSurface()` and `SkCanvas.getSurface()` for making compatible surfaces (typically
used as a workspace and then "saved" with `surface.makeImageSnapshot()`)
### Breaking
- `CanvasKit.MakeWebGLCanvasSurface` no longer takes a webgl context as a first arg, only a
canvas or an id of a canvas. If users want to manage their own GL contexts, they should build
the `SkSurface` themselves with `GetWebGLContext` -> `MakeGrContext` ->
`MakeOnScreenGLSurface`.
## [0.4.1] - 2019-03-01
### Added
- Optional arguments to `MakeManagedAnimation` for supplying external assets (like images, fonts).
## [0.4.0] - 2019-02-25
### Added
- `SkPath.addRoundRect`, `SkPath.reset`, `SkPath.rewind` exposed.
- `SkCanvas.drawArc`, `SkCanvas.drawLine`, `SkCanvas.drawOval`, `SkCanvas.drawRoundRect` exposed.
- Can import/export a SkPath to an array of commands. See `CanvasKit.MakePathFromCmds` and
`SkPath.toCmds`.
- `SkCanvas.drawTextBlob()` and `SkCanvas.SkTextBlob.MakeFromText()` to draw text to a canvas.
- `CanvasKit.TextEncoding` enum. For use with `SkTextBlob`.
- Text shaping with `ShapedText` object and `SkCanvas.drawText`. At compile time, one can choose
between using Harfbuzz/ICU (default) or a primitive one ("primitive_shaper") which just does
line breaking. Using Harfbuzz/ICU substantially increases code size (4.3 MB to 6.4 MB).
### Changed
- `SkCanvas.drawText()` now requires an `SkFont` object for raw strings.
### Removed
- `SkPaint.setTextSize()`, `SkPaint.getTextSize()`, `SkPaint.setTypeface()`
which should be replaced by using `SkFont`.
- Deprecated `CanvasKitInit().then()` interface (see 0.3.1 notes)
### Fixed
- Potential bug in `ready()` if already loaded.
## [0.3.1] - 2019-01-04
### Added
- `SkFont` now exposed.
- `MakeCanvasSurface` can now take a canvas element directly.
- `MakeWebGLCanvasSurface` can now take a WebGL context as an integer and use it directly.
### Changed
- `CanvasKitInit(...).then()` is no longer the recommended way to initialize things.
It will be removed in 0.4.0. Use `CanvasKitInit(...).ready()`, which returns a real Promise.
### Removed
- `SkPaint.measureText` - use `SkFont.measureText` instead.
## [0.3.0] - 2018-12-18
### Added
- Add Canvas2D JS layer. This mirrors the HTML Canvas API. This may be omitted at compile time
it by adding `no_canvas` to the `compile.sh` invocation.
- `CanvasKit.FontMgr.DefaultRef()` and `fontmgr.MakeTypefaceFromData` to load fonts.
- Exposed `SkPath.setVolatile`. Some animations see performance improvements by setting
their paths' volatility to true.
### Fixed
- `SkPath.addRect` now correctly draws counter-clockwise vs clockwise.
### Changed
- `CanvasKit.MakeImageShader` no longer takes encoded bytes, but an `SkImage`, created from
`CanvasKit.MakeImageFromEncoded`. Additionally, the optional parameters `clampIfUnpremul`
and `localMatrix` have been exposed.
- `SkPath.arcTo` now takes `startAngle`, `sweepAngle`, `forceMoveTo` as additional parameters.
- `SkPath.stroke` has a new option `precision` It defaults to 1.0.
- CanvasKit comes with one font (NotoMono) instead of the Skia TestTypeface. Clients are encouraged
to use the new `fontmgr.MakeTypefaceFromData` for more font variety.
### Removed
- `CanvasKit.initFonts()` - no longer needed.
## [0.2.1] - 2018-11-20
Beginning of Changelog history
# Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of
experience, education, socio-economic status, nationality, personal appearance,
race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, or to ban temporarily or permanently any
contributor for other behaviors that they deem inappropriate, threatening,
offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
This Code of Conduct also applies outside the project spaces when the Project
Steward has a reasonable belief that an individual's behavior may have a
negative impact on the project or its community.
## Conflict Resolution
We do not believe that all conflict is bad; healthy debate and disagreement
often yield positive results. However, it is never okay to be disrespectful or
to engage in behavior that violates the project’s code of conduct.
If you see someone violating the code of conduct, you are encouraged to address
the behavior directly with those involved. Many issues can be resolved quickly
and easily, and this gives people more control over the outcome of their
dispute. If you are unable to resolve the matter for any reason, or if the
behavior is threatening or harassing, report it. We are dedicated to providing
an environment where participants feel welcome and safe.
Reports should be directed to [Heather Miller](mailto:hcm@google.com), the
Project Steward(s) for *canvaskit*. It is the Project Steward’s duty to
receive and address reported violations of the code of conduct. They will then
work with a committee consisting of representatives from the Open Source
Programs Office and the Google Open Source Strategy team. If for any reason you
are uncomfortable reaching out the Project Steward, please email
opensource@google.com.
We will investigate every complaint, but you may not receive a direct response.
We will use our discretion in determining when and how to follow up on reported
incidents, which may range from not taking action to permanent expulsion from
the project and project-sponsored spaces. We will notify the accused of the
report and provide them an opportunity to discuss it before any action is taken.
The identity of the reporter will be omitted from the details of the report
supplied to the accused. In potentially harmful situations, such as ongoing
harassment or threats to anyone's safety, we may take action without notice.
## Attribution
This Code of Conduct is adapted from the Contributor Covenant, version 1.4,
available at
https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
# How to Contribute
We'd love to accept your patches and contributions to this project. There are
just a few small guidelines you need to follow.
## Contributor License Agreement
Contributions to this project must be accompanied by a Contributor License
Agreement. You (or your employer) retain the copyright to your contribution;
this simply gives us permission to use and redistribute your contributions as
part of the project. Head over to <https://cla.developers.google.com/> to see
your current agreements on file or to sign a new one.
You generally only need to submit a CLA once, so if you've already submitted one
(even if it was for a different project), you probably don't need to do it
again.
## Code reviews
All submissions, including submissions by project members, require review.
Please see the guidelines for contributing code at https://skia.org/docs/dev/contrib/
<!DOCTYPE html>
<title>CanvasKit (Skia via Web Assembly)</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
canvas, img {
border: 1px dashed #AAA;
}
#api5_c, #api6_c {
width: 300px;
height: 300px;
}
</style>
<h2> Drop in replacement for HTML Canvas (e.g. node.js)</h2>
<img id=api1 width=300 height=300>
<canvas id=api1_c width=300 height=300></canvas>
<img id=api2 width=300 height=300>
<canvas id=api2_c width=300 height=300></canvas>
<img id=api3 width=300 height=300>
<canvas id=api3_c width=300 height=300></canvas>
<img id=api4 width=300 height=300>
<canvas id=api4_c width=300 height=300></canvas>
<img id=api5 width=300 height=300>
<canvas id=api5_c width=300 height=300></canvas>
<img id=api6 width=300 height=300>
<canvas id=api6_c width=300 height=300></canvas>
<img id=api7 width=300 height=300>
<canvas id=api7_c width=300 height=300></canvas>
<img id=api8 width=300 height=300>
<canvas id=api8_c width=300 height=300></canvas>
<h2 id=title1> CanvasKit expands the functionality of a stock HTML canvas</h2>
<canvas id=vertex1 width=300 height=300></canvas>
<canvas id=gradient1 width=300 height=300></canvas>
<canvas id=patheffect width=300 height=300></canvas>
<canvas id=paths width=300 height=300></canvas>
<canvas id=pathperson width=300 height=300></canvas>
<canvas id=ink width=300 height=300></canvas>
<canvas id=surfaces width=300 height=300></canvas>
<canvas id=atlas width=300 height=300></canvas>
<canvas id=decode width=300 height=300></canvas>
<h2 id=title2> CanvasKit can allow for text measurement/placement (e.g. breaking, kerning)</h2>
<canvas id=textonpath width=300 height=300></canvas>
<canvas id=drawGlyphs width=300 height=300></canvas>
<h2 id=title3> Interactive drawPatch</h2>
<canvas id=interdrawpatch width=512 height=512></canvas>
<script type="text/javascript" src="/build/canvaskit.js"></script>
<script type="text/javascript" charset="utf-8" async>
let CanvasKit = null;
const cdn = 'https://cdn.skia.org/misc/';
const ckLoaded = CanvasKitInit({locateFile: (file) => '/build/'+file});
const loadRoboto = fetch(cdn + 'Roboto-Regular.ttf').then((response) => response.arrayBuffer());
const loadNotoSerif = fetch(cdn + 'NotoSerif-Regular.ttf').then((response) => response.arrayBuffer());
const loadTestImage = fetch(cdn + 'test.png').then((response) => response.arrayBuffer());
async function initWebGpu(CK) {
if (navigator.gpu && CK.webgpu) {
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const gpu = CK.MakeGPUDeviceContext(device);
if (!gpu) {
console.error('Failed to initialize WebGPU device context');
}
return gpu;
}
return null;
}
const ready = async function() {
let CK = await ckLoaded;
let gpu = await initWebGpu(CK);
return [CK, gpu];
}();
// Examples which only require canvaskit
ready.then((initData) => {
const [CK, gpu] = initData;
if (gpu) {
let titles = ['title1', 'title2', 'title3'];
titles.forEach((title) => document.getElementById(title).innerText += " (using WebGPU)");
}
CanvasKit = CK;
PathExample(CanvasKit);
InkExample(CanvasKit, gpu);
PathPersonExample(CanvasKit, gpu);
VertexAPI1(CanvasKit, gpu);
GradientAPI1(CanvasKit, gpu);
TextOnPathAPI1(CanvasKit, gpu);
DrawGlyphsAPI1(CanvasKit, gpu);
SurfaceAPI1(CanvasKit, gpu);
if (CanvasKit.MakeCanvas){
CanvasAPI1(CanvasKit);
CanvasAPI2(CanvasKit);
CanvasAPI3(CanvasKit);
CanvasAPI4(CanvasKit);
CanvasAPI5(CanvasKit);
CanvasAPI6(CanvasKit);
CanvasAPI7(CanvasKit);
CanvasAPI8(CanvasKit);
} else {
console.log("Skipping CanvasAPI1 because it's not compiled in");
}
InteractivePatch(CanvasKit, gpu);
});
// Examples requiring external resources
Promise.all([ready, loadRoboto]).then((results) => {DrawingExample(...results.flat())});
Promise.all([ready, loadTestImage]).then((results) => {AtlasAPI1(...results.flat())});
Promise.all([ckLoaded, loadTestImage]).then((results) => {DecodeAPI(...results)});
// Helper function to create an optional WebGPU canvas surface, if WebGPU is supported. Falls back
// to CanvasKit.MakeCanvasSurface for SW/WebGL otherwise.
function MakeCanvasSurface(CanvasKit, gpu, canvasId) {
if (gpu) {
const canvasContext = CanvasKit.MakeGPUCanvasContext(
gpu, document.getElementById(canvasId));
if (!canvasContext) {
console.error('Failed to configure WebGPU canvas context');
return;
}
const surface = CanvasKit.MakeGPUCanvasSurface(canvasContext);
if (!surface) {
console.error('Failed to initialize current swapchain Surface');
}
return surface;
}
return CanvasKit.MakeCanvasSurface(canvasId);
}
function DrawingExample(CanvasKit, gpu, robotoData) {
if (!robotoData || !CanvasKit) {
return;
}
const surface = MakeCanvasSurface(CanvasKit, gpu, 'patheffect');
if (!surface) {
console.error('Could not make surface');
return;
}
const paint = new CanvasKit.Paint();
const roboto = CanvasKit.Typeface.MakeTypefaceFromData(robotoData);
const textPaint = new CanvasKit.Paint();
textPaint.setColor(CanvasKit.RED);
textPaint.setAntiAlias(true);
const textFont = new CanvasKit.Font(roboto, 30);
let i = 0;
let X = 128;
let Y = 128;
function drawFrame(canvas) {
const path = starPath(CanvasKit, X, Y);
const dpe = CanvasKit.PathEffect.MakeDash([15, 5, 5, 10], i/5);
i++;
paint.setPathEffect(dpe);
paint.setStyle(CanvasKit.PaintStyle.Stroke);
paint.setStrokeWidth(5.0 + -3 * Math.cos(i/30));
paint.setAntiAlias(true);
paint.setColor(CanvasKit.Color(66, 129, 164, 1.0));
canvas.clear(CanvasKit.TRANSPARENT);
canvas.drawPath(path, paint);
canvas.drawText('Try Clicking!', 10, 280, textPaint, textFont);
dpe.delete();
path.delete();
surface.requestAnimationFrame(drawFrame);
}
surface.requestAnimationFrame(drawFrame);
// Make animation interactive
let interact = (e) => {
if (!e.pressure) {
return;
}
X = e.offsetX;
Y = e.offsetY;
};
document.getElementById('patheffect').addEventListener('pointermove', interact);
document.getElementById('patheffect').addEventListener('pointerdown', interact);
preventScrolling(document.getElementById('patheffect'));
// A client would need to delete this if it didn't go on for ever.
// paint.delete();
// textPaint.delete();
// textFont.delete();
}
function InteractivePatch(CanvasKit, gpu) {
const ELEM = 'interdrawpatch';
const surface = MakeCanvasSurface(CanvasKit, gpu, ELEM);
if (!surface) {
console.error('Could not make surface');
return;
}
let live_corner, live_index;
const paint = new CanvasKit.Paint();
const pts_paint = new CanvasKit.Paint();
pts_paint.setStyle(CanvasKit.PaintStyle.Stroke);
pts_paint.setStrokeWidth(9);
pts_paint.setStrokeCap(CanvasKit.StrokeCap.Round);
const line_paint = new CanvasKit.Paint();
line_paint.setStyle(CanvasKit.PaintStyle.Stroke);
line_paint.setStrokeWidth(2);
const colors = [CanvasKit.RED, CanvasKit.BLUE, CanvasKit.YELLOW, CanvasKit.CYAN];
const patch = [
[ 10,170, 10, 10, 170, 10], // prev_vector, point, next_vector
[340, 10, 500, 10, 500,170],
[500,340, 500,500, 340,500],
[170,500, 10,500, 10,340],
];
function get_corner(corner, index) {
return [corner[index*2+0], corner[index*2+1]];
}
function push_xy(array, xy) {
array.push(xy[0], xy[1]);
}
function patch_to_cubics(patch) {
const array = [];
push_xy(array, get_corner(patch[0],1));
push_xy(array, get_corner(patch[0],2));
for (let i = 1; i < 4; ++i) {
push_xy(array, get_corner(patch[i],0));
push_xy(array, get_corner(patch[i],1));
push_xy(array, get_corner(patch[i],2));
}
push_xy(array, get_corner(patch[0],0));
return array;
}
function drawFrame(canvas) {
const cubics = patch_to_cubics(patch);
canvas.drawColor(CanvasKit.WHITE);
canvas.drawPatch(cubics, colors, null, CanvasKit.BlendMode.Dst, paint);
if (live_corner) {
canvas.drawPoints(CanvasKit.PointMode.Polygon, live_corner, line_paint);
}
canvas.drawPoints(CanvasKit.PointMode.Points, cubics, pts_paint);
surface.requestAnimationFrame(drawFrame);
}
surface.requestAnimationFrame(drawFrame);
function length2(x, y) {
return x*x + y*y;
}
function hit_test(x,y, x1,y1) {
return length2(x-x1, y-y1) <= 10*10;
}
function pointer_up(e) {
live_corner = null;
live_index = null;
}
function pointer_down(e) {
live_corner = null;
live_index = null;
for (p of patch) {
for (let i = 0; i < 6; i += 2) {
if (hit_test(p[i], p[i+1], e.offsetX, e.offsetY)) {
live_corner = p;
live_index = i;
}
}
}
}
function pointer_move(e) {
if (e.pressure && live_corner) {
if (live_index == 2) {
// corner
const dx = e.offsetX - live_corner[2];
const dy = e.offsetY - live_corner[3];
for (let i = 0; i < 3; ++i) {
live_corner[i*2+0] += dx;
live_corner[i*2+1] += dy;
}
} else {
// control-point
live_corner[live_index+0] = e.offsetX;
live_corner[live_index+1] = e.offsetY;
}
}
}
document.getElementById(ELEM).addEventListener('pointermove', pointer_move);
document.getElementById(ELEM).addEventListener('pointerdown', pointer_down);
document.getElementById(ELEM).addEventListener('pointerup', pointer_up);
preventScrolling(document.getElementById(ELEM));
}
function PathPersonExample(CanvasKit, gpu) {
const surface = MakeCanvasSurface(CanvasKit, gpu, 'pathperson');
if (!surface) {
console.error('Could not make surface');
return;
}
function drawFrame(canvas) {
const paint = new CanvasKit.Paint();
paint.setStrokeWidth(1.0);
paint.setAntiAlias(true);
paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
paint.setStyle(CanvasKit.PaintStyle.Stroke);
const pb = new CanvasKit.PathBuilder();
pb.moveTo(10, 10);
pb.lineTo(100, 10);
pb.moveTo(10, 10);
pb.lineTo(10, 200);
pb.moveTo(10, 100);
pb.lineTo(100,100);
pb.moveTo(10, 200);
pb.lineTo(100, 200);
const path = pb.detach();
canvas.drawPath(path, paint);
path.delete();
pb.delete();
paint.delete();
}
// Intentionally just draw frame once
surface.drawOnce(drawFrame);
}
function PathExample(CanvasKit) {
const surface = CanvasKit.MakeSWCanvasSurface('paths');
if (!surface) {
console.error('Could not make surface');
return;
}
function drawFrame(canvas) {
const paint = new CanvasKit.Paint();
paint.setStrokeWidth(1.0);
paint.setAntiAlias(true);
paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
paint.setStyle(CanvasKit.PaintStyle.Stroke);
const pb = new CanvasKit.PathBuilder();
pb.moveTo(20, 5);
pb.lineTo(30, 20);
pb.lineTo(40, 10);
pb.lineTo(50, 20);
pb.lineTo(60, 0);
pb.lineTo(20, 5);
pb.moveTo(20, 80);
pb.cubicTo(90, 10, 160, 150, 190, 10);
pb.moveTo(36, 148);
pb.quadTo(66, 188, 120, 136);
pb.lineTo(36, 148);
pb.moveTo(150, 180);
pb.arcToTangent(150, 100, 50, 200, 20);
pb.lineTo(160, 160);
pb.moveTo(20, 120);
pb.lineTo(20, 120);
const path = pb.detach();
canvas.drawPath(path, paint);
const rrect = CanvasKit.RRectXY([100, 10, 140, 62], 10, 4);
const rrectPB = new CanvasKit.PathBuilder();
rrectPB.addRRect(rrect, true);
const rrectPath = rrectPB.detach();
canvas.drawPath(rrectPath, paint);
rrectPath.delete();
rrectPB.delete();
pb.delete();
path.delete();
paint.delete();
}
// Intentionally just draw frame once
surface.drawOnce(drawFrame);
}
function preventScrolling(canvas) {
canvas.addEventListener('touchmove', (e) => {
// Prevents touch events in the canvas from scrolling the canvas.
e.preventDefault();
e.stopPropagation();
});
}
function InkExample(CanvasKit, gpu) {
const surface = MakeCanvasSurface(CanvasKit, gpu, 'ink');
if (!surface) {
console.error('Could not make surface');
return;
}
let paint = new CanvasKit.Paint();
paint.setAntiAlias(true);
paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
paint.setStyle(CanvasKit.PaintStyle.Stroke);
paint.setStrokeWidth(4.0);
paint.setPathEffect(CanvasKit.PathEffect.MakeCorner(50));
// Draw I N K
let pb = new CanvasKit.PathBuilder();
pb.moveTo(80, 30);
pb.lineTo(80, 80);
pb.moveTo(100, 80);
pb.lineTo(100, 15);
pb.lineTo(130, 95);
pb.lineTo(130, 30);
pb.moveTo(150, 30);
pb.lineTo(150, 80);
pb.moveTo(170, 30);
pb.lineTo(150, 55);
pb.lineTo(170, 80);
let paths = [pb.detach()];
let paints = [paint];
function drawFrame(canvas) {
canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
for (let i = 0; i < paints.length && i < paths.length; i++) {
canvas.drawPath(paths[i], paints[i]);
}
surface.requestAnimationFrame(drawFrame);
}
let hold = false;
let interact = (e) => {
let type = e.type;
if (type === 'lostpointercapture' || type === 'pointerup' || !e.pressure ) {
hold = false;
return;
}
if (hold) {
pb.lineTo(e.offsetX, e.offsetY);
paths[paths.length-1].delete();
// Snapshot gets us the work-in-progress path but we can still
// add to the path builder if we want.
paths[paths.length-1] = pb.snapshot();
} else {
paint = paint.copy();
paint.setColor(CanvasKit.Color(Math.random() * 255, Math.random() * 255, Math.random() * 255, Math.random() + .2));
paints.push(paint);
paths.push(new CanvasKit.Path());
pb.reset();
pb.moveTo(e.offsetX, e.offsetY);
}
hold = true;
};
document.getElementById('ink').addEventListener('pointermove', interact);
document.getElementById('ink').addEventListener('pointerdown', interact);
document.getElementById('ink').addEventListener('lostpointercapture', interact);
document.getElementById('ink').addEventListener('pointerup', interact);
preventScrolling(document.getElementById('ink'));
surface.requestAnimationFrame(drawFrame);
}
function starPath(CanvasKit, X=128, Y=128, R=116) {
let p = new CanvasKit.PathBuilder();
p.moveTo(X + R, Y);
for (let i = 1; i < 8; i++) {
let a = 2.6927937 * i;
p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a));
}
return p.detachAndDelete();
}
function CanvasAPI1(CanvasKit) {
let skcanvas = CanvasKit.MakeCanvas(300, 300);
let realCanvas = document.getElementById('api1_c');
let skPromise = fetch(cdn + 'test.png')
// if clients want to use a Blob, they are responsible
// for reading it themselves.
.then((response) => response.arrayBuffer())
.then((buffer) => {
skcanvas._img = skcanvas.decodeImage(buffer);
});
let realPromise = fetch(cdn + 'test.png')
.then((response) => response.blob())
.then((blob) => createImageBitmap(blob))
.then((bitmap) => {
realCanvas._img = bitmap;
});
let realFontLoaded = new FontFace('Bungee', 'url(/tests/assets/Bungee-Regular.ttf)', {
'family': 'Bungee',
'style': 'normal',
'weight': '400',
}).load().then((font) => {
document.fonts.add(font);
});
let skFontLoaded = fetch('/tests/assets/Bungee-Regular.ttf').then(
(response) => response.arrayBuffer()).then(
(buffer) => {
// loadFont is synchronous
skcanvas.loadFont(buffer, {
'family': 'Bungee',
'style': 'normal',
'weight': '400',
});
});
Promise.all([realPromise, skPromise, realFontLoaded, skFontLoaded]).then(() => {
for (let canvas of [skcanvas, realCanvas]) {
let ctx = canvas.getContext('2d');
ctx.fillStyle = '#EEE';
ctx.fillRect(0, 0, 300, 300);
ctx.fillStyle = 'black';
ctx.font = '26px Bungee';
ctx.rotate(.1);
let text = ctx.measureText('Awesome');
ctx.fillText('Awesome ', 25, 100);
ctx.strokeText('Groovy!', 35 + text.width, 100);
// Draw line under Awesome
ctx.strokeStyle = 'rgba(125,0,0,0.5)';
ctx.beginPath();
ctx.lineWidth = 6;
ctx.moveTo(25, 105);
ctx.lineTo(200, 105);
ctx.stroke();
// squished vertically
ctx.globalAlpha = 0.7;
ctx.imageSmoothingQuality = 'medium';
ctx.drawImage(canvas._img, 150, 150, 150, 100);
ctx.rotate(-.2);
ctx.imageSmoothingEnabled = false;
ctx.drawImage(canvas._img, 100, 150, 400, 350, 10, 200, 150, 100);
let idata = ctx.getImageData(80, 220, 40, 45);
ctx.putImageData(idata, 250, 10);
ctx.putImageData(idata, 200, 10, 20, 10, 20, 30);
ctx.resetTransform();
ctx.strokeStyle = 'black';
ctx.lineWidth = 1;
ctx.strokeRect(200, 10, 40, 45);
idata = ctx.createImageData(10, 20);
ctx.putImageData(idata, 10, 10);
}
document.getElementById('api1').src = skcanvas.toDataURL();
skcanvas.dispose();
});
}
function CanvasAPI2(CanvasKit) {
let skcanvas = CanvasKit.MakeCanvas(300, 300);
let realCanvas = document.getElementById('api2_c');
realCanvas.width = 300;
realCanvas.height = 300;
// svg data for a clock
skcanvas._path = skcanvas.makePath2D('M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z');
realCanvas._path = new Path2D('M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z');
for (let canvas of [skcanvas, realCanvas]) {
let ctx = canvas.getContext('2d');
ctx.scale(1.5, 1.5);
ctx.moveTo(20, 5);
ctx.lineTo(30, 20);
ctx.lineTo(40, 10);
ctx.lineTo(50, 20);
ctx.lineTo(60, 0);
ctx.lineTo(20, 5);
ctx.moveTo(20, 80);
ctx.bezierCurveTo(90, 10, 160, 150, 190, 10);
ctx.moveTo(36, 148);
ctx.quadraticCurveTo(66, 188, 120, 136);
ctx.lineTo(36, 148);
ctx.rect(5, 170, 20, 25);
ctx.moveTo(150, 180);
ctx.arcTo(150, 100, 50, 200, 20);
ctx.lineTo(160, 160);
ctx.moveTo(20, 120);
ctx.arc(20, 120, 18, 0, 1.75 * Math.PI);
ctx.lineTo(20, 120);
ctx.moveTo(150, 5);
ctx.ellipse(130, 25, 30, 10, -1*Math.PI/8, Math.PI/6, 1.5*Math.PI);
ctx.lineWidth = 4/3;
ctx.stroke();
// make a clock
ctx.stroke(canvas._path);
// Test edgecases and draw direction
ctx.beginPath();
ctx.arc(50, 100, 10, Math.PI, -Math.PI/2);
ctx.stroke();
ctx.beginPath();
ctx.arc(75, 100, 10, Math.PI, -Math.PI/2, true);
ctx.stroke();
ctx.beginPath();
ctx.arc(100, 100, 10, Math.PI, 100.1 * Math.PI, true);
ctx.stroke();
ctx.beginPath();
ctx.arc(125, 100, 10, Math.PI, 100.1 * Math.PI, false);
ctx.stroke();
ctx.beginPath();
ctx.ellipse(155, 100, 10, 15, Math.PI/8, 100.1 * Math.PI, Math.PI, true);
ctx.stroke();
ctx.beginPath();
ctx.ellipse(180, 100, 10, 15, Math.PI/8, Math.PI, 100.1 * Math.PI, true);
ctx.stroke();
}
document.getElementById('api2').src = skcanvas.toDataURL();
skcanvas.dispose();
}
function CanvasAPI3(CanvasKit) {
let skcanvas = CanvasKit.MakeCanvas(300, 300);
let realCanvas = document.getElementById('api3_c');
realCanvas.width = 300;
realCanvas.height = 300;
for (let canvas of [skcanvas, realCanvas]) {
let ctx = canvas.getContext('2d');
ctx.rect(10, 10, 20, 20);
ctx.scale(2.0, 4.0);
ctx.rect(30, 10, 20, 20);
ctx.resetTransform();
ctx.rotate(Math.PI / 3);
ctx.rect(50, 10, 20, 20);
ctx.resetTransform();
ctx.translate(30, -2);
ctx.rect(70, 10, 20, 20);
ctx.resetTransform();
ctx.translate(60, 0);
ctx.rotate(Math.PI / 6);
ctx.transform(1.5, 0, 0, 0.5, 0, 0); // effectively scale
ctx.rect(90, 10, 20, 20);
ctx.resetTransform();
ctx.save();
ctx.setTransform(2, 0, -.5, 2.5, -40, 120);
ctx.rect(110, 10, 20, 20);
ctx.lineTo(110, 0);
ctx.restore();
ctx.lineTo(220, 120);
ctx.scale(3.0, 3.0);
ctx.font = '6pt Noto Mono';
ctx.fillText('This text should be huge', 10, 80);
ctx.resetTransform();
ctx.strokeStyle = 'black';
ctx.lineWidth = 2;
ctx.stroke();
ctx.beginPath();
ctx.moveTo(250, 30);
ctx.lineTo(250, 80);
ctx.scale(3.0, 3.0);
ctx.lineTo(280/3, 90/3);
ctx.closePath();
ctx.strokeStyle = 'black';
ctx.lineWidth = 5;
ctx.stroke();
}
document.getElementById('api3').src = skcanvas.toDataURL();
skcanvas.dispose();
}
function CanvasAPI4(CanvasKit) {
let skcanvas = CanvasKit.MakeCanvas(300, 300);
let realCanvas = document.getElementById('api4_c');
realCanvas.width = 300;
realCanvas.height = 300;
for (let canvas of [skcanvas, realCanvas]) {
let ctx = canvas.getContext('2d');
ctx.strokeStyle = '#000';
ctx.fillStyle = '#CCC';
ctx.shadowColor = 'rebeccapurple';
ctx.shadowBlur = 1;
ctx.shadowOffsetX = 3;
ctx.shadowOffsetY = -8;
ctx.rect(10, 10, 30, 30);
ctx.save();
ctx.strokeStyle = '#C00';
ctx.fillStyle = '#00C';
ctx.shadowBlur = 0;
ctx.shadowColor = 'transparent';
ctx.stroke();
ctx.restore();
ctx.fill();
ctx.beginPath();
ctx.moveTo(36, 148);
ctx.quadraticCurveTo(66, 188, 120, 136);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.shadowColor = '#993366AA';
ctx.shadowOffsetX = 8;
ctx.shadowBlur = 5;
ctx.setTransform(2, 0, -.5, 2.5, -40, 120);
ctx.rect(110, 10, 20, 20);
ctx.lineTo(110, 0);
ctx.resetTransform();
ctx.lineTo(220, 120);
ctx.stroke();
ctx.fillStyle = 'green';
ctx.font = '16pt Noto Mono';
ctx.fillText('This should be shadowed', 20, 80);
ctx.beginPath();
ctx.lineWidth = 6;
ctx.ellipse(10, 290, 30, 30, 0, 0, Math.PI * 2);
ctx.scale(2, 1);
ctx.moveTo(10, 290);
ctx.ellipse(10, 290, 30, 60, 0, 0, Math.PI * 2);
ctx.resetTransform();
ctx.scale(3, 1);
ctx.moveTo(10, 290);
ctx.ellipse(10, 290, 30, 90, 0, 0, Math.PI * 2);
ctx.stroke();
}
document.getElementById('api4').src = skcanvas.toDataURL();
skcanvas.dispose();
}
function CanvasAPI5(CanvasKit) {
let skcanvas = CanvasKit.MakeCanvas(600, 600);
let realCanvas = document.getElementById('api5_c');
realCanvas.width = 600;
realCanvas.height = 600;
for (let canvas of [skcanvas, realCanvas]) {
let ctx = canvas.getContext('2d');
ctx.scale(1.1, 1.1);
ctx.translate(10, 10);
// Shouldn't impact the fillRect calls
ctx.setLineDash([5, 3]);
ctx.fillStyle = 'rgba(200, 0, 100, 0.81)';
ctx.fillRect(20, 30, 100, 100);
ctx.globalAlpha = 0.81;
ctx.fillStyle = 'rgba(200, 0, 100, 1.0)';
ctx.fillRect(120, 30, 100, 100);
// This shouldn't do anything
ctx.globalAlpha = 0.1;
ctx.fillStyle = 'rgba(200, 0, 100, 0.9)';
ctx.globalAlpha = 0.9;
// Intentional no-op to check ordering
ctx.clearRect(220, 30, 100, 100);
ctx.fillRect(220, 30, 100, 100);
ctx.fillRect(320, 30, 100, 100);
ctx.clearRect(330, 40, 80, 80);
ctx.strokeStyle = 'blue';
ctx.lineWidth = 3;
ctx.setLineDash([5, 3]);
ctx.strokeRect(20, 150, 100, 100);
ctx.setLineDash([50, 30]);
ctx.strokeRect(125, 150, 100, 100);
ctx.lineDashOffset = 25;
ctx.strokeRect(230, 150, 100, 100);
ctx.setLineDash([2, 5, 9]);
ctx.strokeRect(335, 150, 100, 100);
ctx.setLineDash([5, 2]);
ctx.moveTo(336, 400);
ctx.quadraticCurveTo(366, 488, 120, 450);
ctx.lineTo(300, 400);
ctx.stroke();
ctx.font = '36pt Noto Mono';
ctx.strokeText('Dashed', 20, 350);
ctx.fillText('Not Dashed', 20, 400);
}
document.getElementById('api5').src = skcanvas.toDataURL();
skcanvas.dispose();
}
function CanvasAPI6(CanvasKit) {
let skcanvas = CanvasKit.MakeCanvas(600, 600);
let realCanvas = document.getElementById('api6_c');
realCanvas.width = 600;
realCanvas.height = 600;
for (let canvas of [skcanvas, realCanvas]) {
let ctx = canvas.getContext('2d');
let rgradient = ctx.createRadialGradient(200, 300, 10, 100, 100, 300);
// Add three color stops
rgradient.addColorStop(0, 'red');
rgradient.addColorStop(0.7, 'white');
rgradient.addColorStop(1, 'blue');
ctx.fillStyle = rgradient;
ctx.globalAlpha = 0.7;
ctx.fillRect(0, 0, 600, 600);
ctx.globalAlpha = 0.95;
ctx.beginPath();
ctx.arc(300, 100, 90, 0, Math.PI*1.66);
ctx.closePath();
ctx.strokeStyle = 'yellow';
ctx.lineWidth = 5;
ctx.stroke();
ctx.save();
ctx.clip();
let lgradient = ctx.createLinearGradient(200, 20, 420, 40);
// Add three color stops
lgradient.addColorStop(0, 'green');
lgradient.addColorStop(0.5, 'cyan');
lgradient.addColorStop(1, 'orange');
ctx.fillStyle = lgradient;
ctx.fillRect(200, 30, 200, 300);
ctx.restore();
ctx.fillRect(550, 550, 40, 40);
}
document.getElementById('api6').src = skcanvas.toDataURL();
skcanvas.dispose();
}
function CanvasAPI7(CanvasKit) {
let skcanvas = CanvasKit.MakeCanvas(300, 300);
let realCanvas = document.getElementById('api7_c');
let skPromise = fetch(cdn + 'test.png')
// if clients want to use a Blob, they are responsible
// for reading it themselves.
.then((response) => response.arrayBuffer())
.then((buffer) => {
skcanvas._img = skcanvas.decodeImage(buffer);
});
let realPromise = fetch(cdn + 'test.png')
.then((response) => response.blob())
.then((blob) => createImageBitmap(blob))
.then((bitmap) => {
realCanvas._img = bitmap;
});
Promise.all([realPromise, skPromise]).then(() => {
for (let canvas of [skcanvas, realCanvas]) {
let ctx = canvas.getContext('2d');
ctx.fillStyle = '#EEE';
ctx.fillRect(0, 0, 300, 300);
ctx.lineWidth = 20;
ctx.scale(0.1, 0.2);
let pattern = ctx.createPattern(canvas._img, 'repeat');
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, 1500, 750);
pattern = ctx.createPattern(canvas._img, 'repeat-x');
ctx.fillStyle = pattern;
ctx.fillRect(1500, 0, 3000, 750);
ctx.globalAlpha = 0.7;
pattern = ctx.createPattern(canvas._img, 'repeat-y');
ctx.fillStyle = pattern;
ctx.fillRect(0, 750, 1500, 1500);
ctx.strokeRect(0, 750, 1500, 1500);
pattern = ctx.createPattern(canvas._img, 'no-repeat');
ctx.fillStyle = pattern;
pattern.setTransform({a: 1, b: -.1, c:.1, d: 0.5, e: 1800, f:800});
ctx.fillRect(0, 0, 3000, 1500);
}
document.getElementById('api7').src = skcanvas.toDataURL();
skcanvas.dispose();
});
}
function CanvasAPI8(CanvasKit) {
let skcanvas = CanvasKit.MakeCanvas(300, 300);
let realCanvas = document.getElementById('api8_c');
function drawPoint(ctx, x, y, color) {
ctx.fillStyle = color;
ctx.fillRect(x, y, 1, 1);
}
const IN = 'purple';
const OUT = 'orange';
const SCALE = 4;
const pts = [[3, 3], [4, 4], [5, 5], [10, 10], [8, 10], [6, 10],
[6.5, 9], [15, 10], [17, 10], [17, 11], [24, 24],
[25, 25], [26, 26], [27, 27]];
const tests = [
{
xOffset: 0,
yOffset: 0,
fillType: 'nonzero',
strokeWidth: 0,
testFn: (ctx, x, y) => ctx.isPointInPath(x * SCALE, y * SCALE, 'nonzero'),
},
{
xOffset: 30,
yOffset: 0,
fillType: 'evenodd',
strokeWidth: 0,
testFn: (ctx, x, y) => ctx.isPointInPath(x * SCALE, y * SCALE, 'evenodd'),
},
{
xOffset: 0,
yOffset: 30,
fillType: null,
strokeWidth: 1,
testFn: (ctx, x, y) => ctx.isPointInStroke(x * SCALE, y * SCALE),
},
{
xOffset: 30,
yOffset: 30,
fillType: null,
strokeWidth: 2,
testFn: (ctx, x, y) => ctx.isPointInStroke(x * SCALE, y * SCALE),
},
];
for (let canvas of [skcanvas, realCanvas]) {
let ctx = canvas.getContext('2d');
ctx.font = '11px Noto Mono';
// Draw some visual aids
ctx.fillText('path-nonzero', 30, 15);
ctx.fillText('path-evenodd', 150, 15);
ctx.fillText('stroke-1px-wide', 30, 130);
ctx.fillText('stroke-2px-wide', 150, 130);
ctx.fillText('purple is IN, orange is OUT', 10, 280);
// Scale up to make single pixels easier to see
ctx.scale(SCALE, SCALE);
for (let test of tests) {
ctx.beginPath();
let xOffset = test.xOffset;
let yOffset = test.yOffset;
ctx.fillStyle = '#AAA';
ctx.lineWidth = test.strokeWidth;
ctx.rect(5+xOffset, 5+yOffset, 20, 20);
ctx.arc(15+xOffset, 15+yOffset, 8, 0, Math.PI*2, false);
if (test.fillType) {
ctx.fill(test.fillType);
} else {
ctx.stroke();
}
for (let pt of pts) {
let [x, y] = pt;
x += xOffset;
y += yOffset;
// naively apply transform when querying because the points queried
// ignore the CTM.
if (test.testFn(ctx, x, y)) {
drawPoint(ctx, x, y, IN);
} else {
drawPoint(ctx, x, y, OUT);
}
}
}
}
document.getElementById('api8').src = skcanvas.toDataURL();
skcanvas.dispose();
}
function VertexAPI1(CanvasKit, gpu) {
const surface = MakeCanvasSurface(CanvasKit, gpu, 'vertex1');
if (!surface) {
console.error('Could not make surface');
return;
}
const canvas = surface.getCanvas();
let paint = new CanvasKit.Paint();
// See https://fiddle.skia.org/c/f48b22eaad1bb7adcc3faaa321754af6
// for original c++ version.
let points = [0, 0, 250, 0, 100, 100, 0, 250];
let colors = [CanvasKit.RED, CanvasKit.BLUE,
CanvasKit.YELLOW, CanvasKit.CYAN];
let vertices = CanvasKit.MakeVertices(CanvasKit.VertexMode.TriangleFan,
points, null, colors,
false /*isVolatile*/);
canvas.drawVertices(vertices, CanvasKit.BlendMode.Dst, paint);
vertices.delete();
// See https://fiddle.skia.org/c/e8bdae9bea3227758989028424fcac3d
// for original c++ version.
points = [300, 300, 50, 300, 200, 200, 300, 50 ];
let texs = [ 0, 0, 0, 250, 250, 250, 250, 0 ];
vertices = CanvasKit.MakeVertices(CanvasKit.VertexMode.TriangleFan,
points, texs, colors);
let shader = CanvasKit.Shader.MakeLinearGradient([0, 0], [250, 0],
colors, null, CanvasKit.TileMode.Clamp);
paint.setShader(shader);
canvas.drawVertices(vertices, CanvasKit.BlendMode.Darken, paint);
surface.flush();
shader.delete();
paint.delete();
surface.delete();
vertices.delete();
}
function GradientAPI1(CanvasKit, gpu) {
const surface = MakeCanvasSurface(CanvasKit, gpu, 'gradient1');
if (!surface) {
console.error('Could not make surface');
return;
}
const canvas = surface.getCanvas();
let paint = new CanvasKit.Paint();
// See https://fiddle.skia.org/c/f48b22eaad1bb7adcc3faaa321754af6
// for original c++ version.
let colors = [CanvasKit.BLUE, CanvasKit.YELLOW, CanvasKit.RED];
let pos = [0, .7, 1.0];
let transform = [2, 0, 0,
0, 2, 0,
0, 0, 1];
let shader = CanvasKit.Shader.MakeRadialGradient([150, 150], 130, colors,
pos, CanvasKit.TileMode.Mirror, transform);
paint.setShader(shader);
const textFont = new CanvasKit.Font(CanvasKit.Typeface.GetDefault(), 75);
const textBlob = CanvasKit.TextBlob.MakeFromText('Radial', textFont);
canvas.drawTextBlob(textBlob, 10, 200, paint);
surface.flush();
paint.delete();
textFont.delete();
textBlob.delete();
shader.delete();
surface.delete();
}
function TextOnPathAPI1(CanvasKit, gpu) {
const surface = MakeCanvasSurface(CanvasKit, gpu, 'textonpath');
if (!surface) {
console.error('Could not make surface');
return;
}
const canvas = surface.getCanvas();
const paint = new CanvasKit.Paint();
paint.setStyle(CanvasKit.PaintStyle.Stroke);
paint.setAntiAlias(true);
const font = new CanvasKit.Font(CanvasKit.Typeface.GetDefault(), 24);
const fontPaint = new CanvasKit.Paint();
fontPaint.setStyle(CanvasKit.PaintStyle.Fill);
fontPaint.setAntiAlias(true);
const arc = new CanvasKit.PathBuilder();
arc.arcToOval(CanvasKit.LTRBRect(20, 40, 280, 300), -160, 140, true);
arc.lineTo(210, 140);
arc.arcToOval(CanvasKit.LTRBRect(20, 0, 280, 260), 160, -140, true);
const str = 'This téxt should follow the curve across contours...';
const path = arc.detach();
const textBlob = CanvasKit.TextBlob.MakeOnPath(str, path, font);
canvas.drawPath(path, paint);
canvas.drawTextBlob(textBlob, 0, 0, fontPaint);
surface.flush();
surface.delete();
textBlob.delete();
arc.delete();
path.delete();
paint.delete();
font.delete();
fontPaint.delete();
}
function DrawGlyphsAPI1(CanvasKit, gpu) {
const surface = MakeCanvasSurface(CanvasKit, gpu, 'drawGlyphs');
if (!surface) {
console.error('Could not make surface');
return;
}
const canvas = surface.getCanvas();
const paint = new CanvasKit.Paint();
const font = new CanvasKit.Font(CanvasKit.Typeface.GetDefault(), 16);
paint.setAntiAlias(true);
let glyphs = [];
let positions = [];
for (let i = 0; i < 256; ++i) {
glyphs.push(i);
positions.push((i % 16) * 16);
positions.push(Math.round(i/16) * 16);
}
canvas.drawGlyphs(glyphs, positions, 16, 20, font, paint);
surface.flush();
surface.delete();
paint.delete();
font.delete();
}
function SurfaceAPI1(CanvasKit, gpu) {
const surface = MakeCanvasSurface(CanvasKit, gpu, 'surfaces');
if (!surface) {
console.error('Could not make surface');
return;
}
// create a subsurface as a temporary workspace.
const subSurface = surface.makeSurface({
width: 50,
height: 50,
alphaType: CanvasKit.AlphaType.Premul,
colorType: CanvasKit.ColorType.RGBA_8888,
colorSpace: CanvasKit.ColorSpace.SRGB,
});
if (!subSurface) {
console.error('Could not make subsurface');
return;
}
// draw a small "scene"
const paint = new CanvasKit.Paint();
paint.setColor(CanvasKit.Color(139, 228, 135, 0.95)); // greenish
paint.setStyle(CanvasKit.PaintStyle.Fill);
paint.setAntiAlias(true);
const subCanvas = subSurface.getCanvas();
subCanvas.clear(CanvasKit.BLACK);
subCanvas.drawRect(CanvasKit.LTRBRect(5, 15, 45, 40), paint);
paint.setColor(CanvasKit.Color(214, 93, 244)); // purplish
for (let i = 0; i < 10; i++) {
const x = Math.random() * 50;
const y = Math.random() * 50;
subCanvas.drawOval(CanvasKit.XYWHRect(x, y, 6, 6), paint);
}
// Snap it off as an Image - this image will be in the form the
// parent surface prefers (e.g. Texture for GPU / Raster for CPU).
const img = subSurface.makeImageSnapshot();
// clean up the temporary surface (which also cleans up subCanvas)
subSurface.delete();
paint.delete();
// Make it repeat a bunch with a shader
const pattern = img.makeShaderCubic(CanvasKit.TileMode.Repeat, CanvasKit.TileMode.Mirror,
1/3, 1/3);
const patternPaint = new CanvasKit.Paint();
patternPaint.setShader(pattern);
let i = 0;
function drawFrame(canvas) {
i++;
canvas.clear(CanvasKit.WHITE);
canvas.drawOval(CanvasKit.LTRBRect(i % 60, i % 60, 300 - (i% 60), 300 - (i % 60)), patternPaint);
surface.requestAnimationFrame(drawFrame);
}
surface.requestAnimationFrame(drawFrame);
}
function AtlasAPI1(CanvasKit, gpu, imgData) {
if (!CanvasKit || !imgData) {
return;
}
const surface = MakeCanvasSurface(CanvasKit, gpu, 'atlas');
if (!surface) {
console.error('Could not make surface');
return;
}
const img = CanvasKit.MakeImageFromEncoded(imgData);
const paint = new CanvasKit.Paint();
paint.setColor(CanvasKit.Color(0, 0, 0, 0.8));
// Allocate space for 2 rectangles.
const srcs = CanvasKit.Malloc(Float32Array, 8);
srcs.toTypedArray().set([
0, 0, 250, 250, // LTRB
250, 0, 500, 250
]);
// Allocate space for 2 RSXForms
const dsts = CanvasKit.Malloc(Float32Array, 8);
dsts.toTypedArray().set([
.5, 0, 0, 0, // scos, ssin, tx, ty
0, .8, 200, 100
]);
// Allocate space for 4 colors.
const colors = new CanvasKit.Malloc(Uint32Array, 2);
colors.toTypedArray().set([
CanvasKit.ColorAsInt( 85, 170, 10, 128), // light green
CanvasKit.ColorAsInt( 51, 51, 191, 128), // light blue
]);
let i = 0;
function drawFrame(canvas) {
canvas.clear(CanvasKit.WHITE);
i++;
let scale = 0.5 + Math.sin(i/40)/4;
// update the coordinates of existing sprites - note that this
// does not require a full re-copy of the full array; they are
// updated in-place.
dsts.toTypedArray().set([0.5, 0, (2*i)%200, (5*Math.round(i/200)) % 200], 0);
dsts.toTypedArray().set([scale*Math.sin(i/20), scale*Math.cos(i/20), 200, 100], 4);
canvas.drawAtlas(img, srcs, dsts, paint, CanvasKit.BlendMode.Plus, colors,
{filter: CanvasKit.FilterMode.Nearest});
surface.requestAnimationFrame(drawFrame);
}
surface.requestAnimationFrame(drawFrame);
}
async function DecodeAPI(CanvasKit, imgData) {
if (!CanvasKit || !imgData) {
return;
}
const surface = CanvasKit.MakeCanvasSurface('decode');
if (!surface) {
console.error('Could not make surface');
return;
}
const blob = new Blob([ imgData ]);
// ImageBitmap is not supported in Safari
const imageBitmap = await createImageBitmap(blob);
const img = await CanvasKit.MakeImageFromCanvasImageSource(imageBitmap);
surface.drawOnce((canvas) => {
canvas.drawImage(img, 0, 0, null);
});
}
</script>
<!DOCTYPE html>
<title>CanvasKit Extra features (Skia via Web Assembly)</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
canvas {
border: 1px dashed #AAA;
}
#sk_legos,#sk_drinks,#sk_party,#sk_onboarding, #sk_animated_gif {
width: 300px;
height: 300px;
}
</style>
<h2> Skottie </h2>
<canvas id=sk_legos width=300 height=300></canvas>
<canvas id=sk_drinks width=500 height=500></canvas>
<canvas id=sk_party width=500 height=500></canvas>
<canvas id=sk_onboarding width=500 height=500></canvas>
<canvas id=sk_animated_gif width=500 height=500
title='This is an animated gif being animated in Skottie'></canvas>
<h2> RT Shader </h2>
<canvas id=rtshader width=300 height=300></canvas>
<canvas id=rtshader2 width=300 height=300></canvas>
<h2> Paragraph </h2>
<canvas id=para1 width=600 height=600></canvas>
<canvas id=para2 width=600 height=600 tabindex='-1'></canvas>
<canvas id=para3 width=600 height=600 tabindex='-1'></canvas>
<canvas id=para4 width=600 height=600 tabindex='-1'></canvas>
``
<h2> CanvasKit can serialize/deserialize .skp files</h2>
<canvas id=skp width=500 height=500></canvas>
<h2> 3D perspective transformations </h2>
<canvas id=glyphgame width=500 height=500></canvas>
<h2> Support for extended color spaces </h2>
<a href="chrome://flags/#force-color-profile">Force P3 profile</a>
<canvas id=colorsupport width=300 height=300></canvas>
<script type="text/javascript" src="/build/canvaskit.js"></script>
<script type="text/javascript" src="textapi_utils.js"></script>
<script type="text/javascript" charset="utf-8">
var CanvasKit = null;
var cdn = 'https://cdn.skia.org/misc/';
const ckLoaded = CanvasKitInit({locateFile: (file) => '/build/'+file});
const loadLegoJSON = fetch(cdn + 'lego_loader.json').then((response) => response.text());
const loadDrinksJSON = fetch(cdn + 'drinks.json').then((response) => response.text());
const loadConfettiJSON = fetch(cdn + 'confetti.json').then((response) => response.text());
const loadOnboardingJSON = fetch(cdn + 'onboarding.json').then((response) => response.text());
const loadMultiframeJSON = fetch(cdn + 'skottie_sample_multiframe.json').then((response) => response.text());
const loadFlightGif = fetch(cdn + 'flightAnim.gif').then((response) => response.arrayBuffer());
const loadFont = fetch(cdn + 'Roboto-Regular.ttf').then((response) => response.arrayBuffer());
const loadDog = fetch(cdn + 'dog.jpg').then((response) => response.arrayBuffer());
const loadMandrill = fetch(cdn + 'mandrill_256.png').then((response) => response.arrayBuffer());
const loadBrickTex = fetch(cdn + 'brickwork-texture.jpg').then((response) => response.arrayBuffer());
const loadBrickBump = fetch(cdn + 'brickwork_normal-map.jpg').then((response) => response.arrayBuffer());
// Examples which only require canvaskit
ckLoaded.then((CK) => {
CanvasKit = CK;
RTShaderAPI1(CanvasKit);
ColorSupport(CanvasKit);
SkpExample(CanvasKit);
});
// Examples requiring external resources.
// Set bounds to fix the 4:3 resolution of the legos
Promise.all([ckLoaded, loadLegoJSON]).then(([ck, jsonstr]) => {
SkottieExample(ck, 'sk_legos', jsonstr, [-50, 0, 350, 300]);
});
// Re-size to fit
let fullBounds = [0, 0, 500, 500];
Promise.all([ckLoaded, loadDrinksJSON]).then(([ck, jsonstr]) => {
SkottieExample(ck, 'sk_drinks', jsonstr, fullBounds);
});
Promise.all([ckLoaded, loadConfettiJSON]).then(([ck, jsonstr]) => {
SkottieExample(ck, 'sk_party', jsonstr, fullBounds);
});
Promise.all([ckLoaded, loadOnboardingJSON]).then(([ck, jsonstr]) => {
SkottieExample(ck, 'sk_onboarding', jsonstr, fullBounds);
});
Promise.all([ckLoaded, loadMultiframeJSON, loadFlightGif]).then(([ck, jsonstr, gif]) => {
SkottieExample(ck, 'sk_animated_gif', jsonstr, fullBounds, {'image_0.png': gif});
});
Promise.all([ckLoaded, loadFont]).then((results) => {
ParagraphExtended(...results);
ParagraphAPI1(...results);
ParagraphAPI2(...results);
ParagraphAPI3(...results);
GlyphGame(...results)
});
const rectLeft = 0;
const rectTop = 1;
const rectRight = 2;
const rectBottom = 3;
function SkottieExample(CanvasKit, id, jsonStr, bounds, assets) {
if (!CanvasKit || !jsonStr) {
return;
}
const animation = CanvasKit.MakeManagedAnimation(jsonStr, assets);
const duration = animation.duration() * 1000;
const size = animation.size();
let c = document.getElementById(id);
bounds = bounds || CanvasKit.LTRBRect(0, 0, size.w, size.h);
// Basic managed animation test.
if (id === 'sk_drinks') {
animation.setColor('BACKGROUND_FILL', CanvasKit.Color(0, 163, 199, 1.0));
}
const surface = CanvasKit.MakeCanvasSurface(id);
if (!surface) {
console.error('Could not make surface');
return;
}
let firstFrame = Date.now();
function drawFrame(canvas) {
let seek = ((Date.now() - firstFrame) / duration) % 1.0;
let damage = animation.seek(seek);
if (damage[rectRight] > damage[rectLeft] && damage[rectBottom] > damage[rectTop]) {
canvas.clear(CanvasKit.WHITE);
animation.render(canvas, bounds);
}
surface.requestAnimationFrame(drawFrame);
}
surface.requestAnimationFrame(drawFrame);
return surface;
}
function ParagraphAPI1(CanvasKit, fontData) {
if (!CanvasKit || !fontData) {
return;
}
const surface = CanvasKit.MakeCanvasSurface('para1');
if (!surface) {
console.error('Could not make surface');
return;
}
const canvas = surface.getCanvas();
const fontMgr = CanvasKit.FontMgr.FromData([fontData]);
const paraStyle = new CanvasKit.ParagraphStyle({
textStyle: {
color: CanvasKit.BLACK,
fontFamilies: ['Roboto'],
fontSize: 50,
},
textAlign: CanvasKit.TextAlign.Left,
maxLines: 5,
});
const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
builder.addText('The quick brown fox ate a hamburgerfons and got sick.');
const paragraph = builder.build();
let wrapTo = 0;
let X = 100;
let Y = 100;
const fontPaint = new CanvasKit.Paint();
fontPaint.setStyle(CanvasKit.PaintStyle.Fill);
fontPaint.setAntiAlias(true);
function drawFrame(canvas) {
canvas.clear(CanvasKit.WHITE);
wrapTo = 350 + 150 * Math.sin(Date.now() / 2000);
paragraph.layout(wrapTo);
canvas.drawParagraph(paragraph, 0, 0);
canvas.drawLine(wrapTo, 0, wrapTo, 400, fontPaint);
surface.requestAnimationFrame(drawFrame);
}
surface.requestAnimationFrame(drawFrame);
let interact = (e) => {
X = e.offsetX*2; // multiply by 2 because the canvas is 300 css pixels wide,
Y = e.offsetY*2; // but the canvas itself is 600px wide
};
document.getElementById('para1').addEventListener('pointermove', interact);
return surface;
}
function ParagraphAPI2(CanvasKit, fontData) {
if (!CanvasKit || !fontData) {
return;
}
const surface = CanvasKit.MakeCanvasSurface('para2');
if (!surface) {
console.error('Could not make surface');
return;
}
const mouse = MakeMouse();
const cursor = MakeCursor(CanvasKit);
const canvas = surface.getCanvas();
const text0 = "In a hole in the ground there lived a hobbit. Not a nasty, dirty, " +
"wet hole full of worms and oozy smells. This was a hobbit-hole and " +
"that means good food, a warm hearth, and all the comforts of home.";
const LOC_X = 20,
LOC_Y = 20;
const bgPaint = new CanvasKit.Paint();
bgPaint.setColor([0.965, 0.965, 0.965, 1]);
const editor = MakeEditor(text0, {typeface:CanvasKit.Typeface.GetDefault(), size:24}, cursor, 400);
editor.applyStyleToRange({size:100}, 0, 1);
editor.applyStyleToRange({italic:true}, 38, 38+6);
editor.applyStyleToRange({color:[1,0,0,1]}, 5, 5+4);
editor.setXY(LOC_X, LOC_Y);
function drawFrame(canvas) {
const lines = editor.getLines();
canvas.clear(CanvasKit.WHITE);
if (mouse.isActive()) {
const pos = mouse.getPos(-LOC_X, -LOC_Y);
const a = lines_pos_to_index(lines, pos[0], pos[1]);
const b = lines_pos_to_index(lines, pos[2], pos[3]);
if (a == b) {
editor.setIndex(a);
} else {
editor.setIndices(a, b);
}
}
canvas.drawRect(editor.bounds(), bgPaint);
editor.draw(canvas);
surface.requestAnimationFrame(drawFrame);
}
surface.requestAnimationFrame(drawFrame);
function interact(e) {
const type = e.type;
if (type === 'pointerup') {
mouse.setUp(e.offsetX, e.offsetY);
} else if (type === 'pointermove') {
mouse.setMove(e.offsetX, e.offsetY);
} else if (type === 'pointerdown') {
mouse.setDown(e.offsetX, e.offsetY);
}
};
function keyhandler(e) {
switch (e.key) {
case 'ArrowLeft': editor.moveDX(-1); return;
case 'ArrowRight': editor.moveDX(1); return;
case 'ArrowUp':
e.preventDefault();
editor.moveDY(-1);
return;
case 'ArrowDown':
e.preventDefault();
editor.moveDY(1);
return;
case 'Backspace':
editor.deleteSelection();
return;
case 'Shift':
return;
}
if (e.ctrlKey) {
switch (e.key) {
case 'r': editor.applyStyleToSelection({color:[1,0,0,1]}); return;
case 'g': editor.applyStyleToSelection({color:[0,0.6,0,1]}); return;
case 'u': editor.applyStyleToSelection({color:[0,0,1,1]}); return;
case 'k': editor.applyStyleToSelection({color:[0,0,0,1]}); return;
case 'i': editor.applyStyleToSelection({italic:'toggle'}); return;
case 'b': editor.applyStyleToSelection({bold:'toggle'}); return;
case 'l': editor.applyStyleToSelection({underline:'toggle'}); return;
case ']': editor.applyStyleToSelection({size_add:1}); return;
case '[': editor.applyStyleToSelection({size_add:-1}); return;
}
}
if (!e.ctrlKey && !e.metaKey) {
e.preventDefault(); // at least needed for 'space'
editor.insert(e.key);
}
}
document.getElementById('para2').addEventListener('pointermove', interact);
document.getElementById('para2').addEventListener('pointerdown', interact);
document.getElementById('para2').addEventListener('pointerup', interact);
document.getElementById('para2').addEventListener('keydown', keyhandler);
return surface;
}
function ParagraphAPI3(CanvasKit, fontData) {
if (!CanvasKit || !fontData) {
return;
}
const surface = CanvasKit.MakeCanvasSurface('para3');
if (!surface) {
console.error('Could not make surface');
return;
}
const fontMgr = CanvasKit.FontMgr.FromData([fontData]);
const paraStyle = new CanvasKit.ParagraphStyle({
textStyle: {
color: CanvasKit.BLACK,
fontFamilies: ['Roboto'],
fontSize: 50,
},
textAlign: CanvasKit.TextAlign.Left,
maxLines: 5,
});
const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
builder.addText('The quick brown fox ate a hamburgerfons and got sick.');
// The code below works for English-only text assuming
// that each character is a glyph (cluster), spaces are breaks between words,
// new lines are breaks between lines and the entire text is LTR.
// In case the paragraph text was constructed in a set of calls we need the text
const text = builder.getText();
// Pass the entire text as one word. It's only used for the method
// getWords
const mallocedWords = CanvasKit.Malloc(Uint32Array, 2);
mallocedWords.toTypedArray().set([0, text.length]);
// Pass each character as a separate grapheme
const mallocedGraphemes = CanvasKit.Malloc(Uint32Array, text.length + 1);
const graphemesArr = mallocedGraphemes.toTypedArray();
for (let i = 0; i <= text.length; i++) {
graphemesArr[i] = i;
}
// Pass each space as a "soft" break and each new line as a "hard" break.
const SOFT = 0;
const HARD = 1;
const lineBreaks = [0, SOFT];
for (let i = 0; i < text.length; ++i) {
if (text[i] === ' ') {
lineBreaks.push(i + 1, SOFT);
}
if (text[i] === '\n') {
lineBreaks.push(i + 1, HARD);
}
}
lineBreaks.push(text.length, SOFT);
const mallocedLineBreaks = CanvasKit.Malloc(Uint32Array, lineBreaks.length);
mallocedLineBreaks.toTypedArray().set(lineBreaks);
builder.setWordsUtf16(mallocedWords);
builder.setGraphemeBreaksUtf16(mallocedGraphemes);
builder.setLineBreaksUtf16(mallocedLineBreaks);
const paragraph = builder.build();
paragraph.layout(600);
let wrapTo = 0;
let X = 100;
let Y = 100;
const fontPaint = new CanvasKit.Paint();
fontPaint.setStyle(CanvasKit.PaintStyle.Fill);
fontPaint.setAntiAlias(true);
function drawFrame(canvas) {
canvas.clear(CanvasKit.WHITE);
wrapTo = 350 + 150 * Math.sin(Date.now() / 2000);
paragraph.layout(wrapTo);
canvas.drawParagraph(paragraph, 0, 0);
canvas.drawLine(wrapTo, 0, wrapTo, 400, fontPaint);
surface.requestAnimationFrame(drawFrame);
}
surface.requestAnimationFrame(drawFrame);
let interact = (e) => {
X = e.offsetX*2; // multiply by 2 because the canvas is 300 css pixels wide,
Y = e.offsetY*2; // but the canvas itself is 600px wide
};
document.getElementById('para1').addEventListener('pointermove', interact);
return surface;
}
function ParagraphExtended(CanvasKit, fontData) {
if (!CanvasKit || !fontData) {
return;
}
const surface = CanvasKit.MakeCanvasSurface('para4');
if (!surface) {
console.error('Could not make surface');
return;
}
const fontMgr = CanvasKit.FontMgr.FromData([fontData]);
const paraStyle = new CanvasKit.ParagraphStyle({
textStyle: {
color: CanvasKit.BLACK,
fontFamilies: ['Roboto'],
fontSize: 50,
},
textAlign: CanvasKit.TextAlign.Left,
maxLines: 5,
});
const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
builder.addText('The quick brown fox ate a hamburgerfons and got sick.');
// The code below works for English-only text assuming
// that each character is a glyph (cluster), spaces are breaks between words,
// new lines are breaks between lines and the entire text is LTR.
// In case the paragraph text was constructed in a set of calls we need the text
const text = builder.getText();
// Pass the entire text as one word. It's only used for the method getWords
const words = [0, text.length];
// Pass each character as a separate grapheme
const graphemes = [];
for (let i = 0; i <= text.length; i++) {
graphemes.push(i);
}
// Pass each space as a "soft" break and each new line as a "hard" break
const SOFT = 0;
const HARD = 1;
const lineBreaks = [0, SOFT];
for (let i = 0; i < text.length; ++i) {
if (text[i] === ' ') {
lineBreaks.push(i + 1, SOFT);
}
if (text[i] === '\n') {
lineBreaks.push(i + 1, HARD);
}
}
lineBreaks.push(text.length, SOFT);
builder.setWordsUtf16(words);
builder.setGraphemeBreaksUtf16(graphemes);
builder.setLineBreaksUtf16(lineBreaks);
const paragraph = builder.build();
paragraph.layout(600);
let wrapTo = 0;
let X = 100;
let Y = 100;
const roboto = CanvasKit.Typeface.MakeTypefaceFromData(fontData);
const textFont = new CanvasKit.Font(roboto, 20);
const fontPaint = new CanvasKit.Paint();
fontPaint.setStyle(CanvasKit.PaintStyle.Fill);
fontPaint.setAntiAlias(true);
function drawFrame(canvas) {
canvas.clear(CanvasKit.WHITE);
wrapTo = 350 + 150 * Math.sin(Date.now() / 2000);
paragraph.layout(wrapTo);
canvas.drawParagraph(paragraph, 0, 0);
canvas.drawLine(wrapTo, 0, wrapTo, 400, fontPaint);
const lines = paragraph.getShapedLines();
var fonts = '';
var sep = '';
for (const line of lines) {
for (const run of line.runs) {
fonts += sep;
fonts += run.typeface.getFamilyName();
sep = ', ';
}
}
canvas.drawText(fonts, wrapTo + 10, 200, fontPaint, textFont);
surface.requestAnimationFrame(drawFrame);
}
surface.requestAnimationFrame(drawFrame);
let interact = (e) => {
X = e.offsetX*2; // multiply by 2 because the canvas is 300 css pixels wide,
Y = e.offsetY*2; // but the canvas itself is 600px wide
};
document.getElementById('para1').addEventListener('pointermove', interact);
return surface;
}
const spiralSkSL = `
uniform float rad_scale;
uniform float2 in_center;
uniform float4 in_colors0;
uniform float4 in_colors1;
half4 main(float2 p) {
float2 pp = p - in_center;
float radius = sqrt(dot(pp, pp));
radius = sqrt(radius);
float angle = atan(pp.y / pp.x);
float t = (angle + 3.1415926/2) / (3.1415926);
t += radius * rad_scale;
t = fract(t);
return half4(mix(in_colors0, in_colors1, t));
}`;
function RTShaderAPI1(CanvasKit) {
if (!CanvasKit) {
return;
}
const surface = CanvasKit.MakeCanvasSurface('rtshader');
if (!surface) {
console.error('Could not make surface');
return;
}
const canvas = surface.getCanvas();
const effect = CanvasKit.RuntimeEffect.Make(spiralSkSL);
const shader = effect.makeShader([
0.5,
150, 150,
0, 1, 0, 1,
1, 0, 0, 1]);
const paint = new CanvasKit.Paint();
paint.setShader(shader);
canvas.drawRect(CanvasKit.LTRBRect(0, 0, 300, 300), paint);
surface.flush();
shader.delete();
paint.delete();
effect.delete();
}
// RTShader2 demo
Promise.all([ckLoaded, loadDog, loadMandrill]).then((values) => {
const [CanvasKit, dogData, mandrillData] = values;
const dogImg = CanvasKit.MakeImageFromEncoded(dogData);
if (!dogImg) {
console.error('could not decode dog');
return;
}
const mandrillImg = CanvasKit.MakeImageFromEncoded(mandrillData);
if (!mandrillImg) {
console.error('could not decode mandrill');
return;
}
const quadrantSize = 150;
const dogShader = dogImg.makeShaderCubic(
CanvasKit.TileMode.Clamp, CanvasKit.TileMode.Clamp,
1/3, 1/3,
CanvasKit.Matrix.scaled(quadrantSize/dogImg.width(),
quadrantSize/dogImg.height()));
const mandrillShader = mandrillImg.makeShaderCubic(
CanvasKit.TileMode.Clamp, CanvasKit.TileMode.Clamp,
1/3, 1/3,
CanvasKit.Matrix.scaled(
quadrantSize/mandrillImg.width(),
quadrantSize/mandrillImg.height()));
const surface = CanvasKit.MakeCanvasSurface('rtshader2');
if (!surface) {
console.error('Could not make surface');
return;
}
const prog = `
uniform shader before_map;
uniform shader after_map;
uniform shader threshold_map;
uniform float cutoff;
uniform float slope;
float smooth_cutoff(float x) {
x = x * slope + (0.5 - slope * cutoff);
return clamp(x, 0, 1);
}
half4 main(float2 xy) {
half4 before = before_map.eval(xy);
half4 after = after_map.eval(xy);
float m = smooth_cutoff(threshold_map.eval(xy).r);
return mix(before, after, half(m));
}`;
const canvas = surface.getCanvas();
const thresholdEffect = CanvasKit.RuntimeEffect.Make(prog);
const spiralEffect = CanvasKit.RuntimeEffect.Make(spiralSkSL);
const draw = (x, y, shader) => {
const paint = new CanvasKit.Paint();
paint.setShader(shader);
canvas.save();
canvas.translate(x, y);
canvas.drawRect(CanvasKit.LTRBRect(0, 0, quadrantSize, quadrantSize), paint);
canvas.restore();
paint.delete();
};
const offscreenSurface = CanvasKit.MakeSurface(quadrantSize, quadrantSize);
const getBlurrySpiralShader = (rad_scale) => {
const oCanvas = offscreenSurface.getCanvas();
const spiralShader = spiralEffect.makeShader([
rad_scale,
quadrantSize/2, quadrantSize/2,
1, 1, 1, 1,
0, 0, 0, 1]);
const blur = CanvasKit.ImageFilter.MakeBlur(0.1, 0.1, CanvasKit.TileMode.Clamp, null);
const paint = new CanvasKit.Paint();
paint.setShader(spiralShader);
paint.setImageFilter(blur);
oCanvas.drawRect(CanvasKit.LTRBRect(0, 0, quadrantSize, quadrantSize), paint);
paint.delete();
blur.delete();
spiralShader.delete();
return offscreenSurface.makeImageSnapshot()
.makeShaderCubic(CanvasKit.TileMode.Clamp, CanvasKit.TileMode.Clamp,
1/3, 1/3);
};
const drawFrame = () => {
surface.requestAnimationFrame(drawFrame);
const thresholdShader = getBlurrySpiralShader(Math.sin(Date.now() / 5000) / 2);
const blendShader = thresholdEffect.makeShaderWithChildren(
[0.5, 10],
[dogShader, mandrillShader, thresholdShader]);
draw(0, 0, blendShader);
draw(quadrantSize, 0, thresholdShader);
draw(0, quadrantSize, dogShader);
draw(quadrantSize, quadrantSize, mandrillShader);
blendShader.delete();
};
surface.requestAnimationFrame(drawFrame);
});
function SkpExample(CanvasKit) {
if (!CanvasKit) {
return;
}
const surface = CanvasKit.MakeSWCanvasSurface('skp');
if (!surface) {
console.error('Could not make surface');
return;
}
const paint = new CanvasKit.Paint();
paint.setColor(CanvasKit.RED);
const textPaint = new CanvasKit.Paint();
const textFont = new CanvasKit.Font(CanvasKit.Typeface.GetDefault(), 20);
const pr = new CanvasKit.PictureRecorder();
const skpCanvas = pr.beginRecording(CanvasKit.LTRBRect(0, 0, 200, 200));
skpCanvas.drawRect(CanvasKit.LTRBRect(10, 10, 50, 50), paint);
skpCanvas.drawText('If you see this, CanvasKit loaded!!', 5, 100, textPaint, textFont);
const pic = pr.finishRecordingAsPicture();
const skpData = pic.serialize();
paint.delete();
pr.delete();
const deserialized = CanvasKit.MakePicture(skpData);
function drawFrame(canvas) {
if (deserialized) {
canvas.drawPicture(deserialized);
} else {
canvas.drawText('SKP did not deserialize', 5, 100, textPaint, textFont);
}
}
surface.drawOnce(drawFrame);
textPaint.delete();
textFont.delete();
}
// Shows a hidden message by rotating all the characters in a kind of way that makes you
// search with your mouse.
function GlyphGame(canvas, robotoData) {
const surface = CanvasKit.MakeCanvasSurface('glyphgame');
if (!surface) {
console.error('Could not make surface');
return;
}
const sizeX = document.getElementById('glyphgame').width;
const sizeY = document.getElementById('glyphgame').height;
const halfDim = Math.min(sizeX, sizeY) / 2;
const margin = 50;
const marginTop = 25;
let rotX = 0; // expected to be updated in interact()
let rotY = 0;
let pointer = [500, 450];
const radPerPixel = 0.005; // radians of subject rotation per pixel distance moved by mouse.
const camAngle = Math.PI / 12;
const cam = {
'eye' : [0, 0, 1 / Math.tan(camAngle/2) - 1],
'coa' : [0, 0, 0],
'up' : [0, 1, 0],
'near' : 0.02,
'far' : 4,
'angle': camAngle,
};
let lastImage = null;
const fontMgr = CanvasKit.FontMgr.FromData([robotoData]);
const paraStyle = new CanvasKit.ParagraphStyle({
textStyle: {
color: CanvasKit.Color(105, 56, 16), // brown
fontFamilies: ['Roboto'],
fontSize: 28,
},
textAlign: CanvasKit.TextAlign.Left,
});
const hStyle = CanvasKit.RectHeightStyle.Max;
const wStyle = CanvasKit.RectWidthStyle.Tight;
const quotes = [
'Some activities superficially familiar to you are merely stupid and should be avoided for your safety, although they are not illegal as such. These include: giving your bank account details to the son of the Nigerian Minister of Finance; buying title to bridges, skyscrapers, spacecraft, planets, or other real assets; murder; selling your identity; and entering into financial contracts with entities running Economics 2.0 or higher.',
// Charles Stross - Accelerando
'If only there were evil people somewhere insidiously committing evil deeds, and it were necessary only to separate them from the rest of us and destroy them. But the line dividing good and evil cuts through the heart of every human being. And who is willing to destroy a piece of his own heart?',
// Aleksandr Solzhenitsyn - The Gulag Archipelago
'There is one metaphor of which the moderns are very fond; they are always saying, “You can’t put the clock back.” The simple and obvious answer is “You can.” A clock, being a piece of human construction, can be restored by the human finger to any figure or hour. In the same way society, being a piece of human construction, can be reconstructed upon any plan that has ever existed.',
// G. K. Chesterton - What's Wrong With The World?
];
// pick one at random
const text = quotes[Math.floor(Math.random()*3)];
const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
builder.addText(text);
const paragraph = builder.build();
const font = new CanvasKit.Font(CanvasKit.Typeface.GetDefault(), 18);
// wrap the text to a given width.
paragraph.layout(sizeX - margin*2);
// to rotate every glyph individually, calculate the bounding rect of each one,
// construct an array of rects and paragraphs that would draw each glyph individually.
const letters = Array(text.length);
for (let i = 0; i < text.length; i++) {
const r = paragraph.getRectsForRange(i, i+1, hStyle, wStyle)[0];
// The character is drawn with drawParagraph so we can pass the paraStyle,
// and have our character be the exact size and shape the paragraph expected
// when it wrapped the text. canvas.drawText wouldn't cut it.
const tmpbuilder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
tmpbuilder.addText(text[i]);
const para = tmpbuilder.build();
para.layout(100);
letters[i] = {
'r': r,
'para': para,
};
}
function drawFrame(canvas) {
// persistence of vision effect is done by drawing the past frame as an image,
// then covering with semitransparent background color.
if (lastImage) {
canvas.drawImage(lastImage, 0, 0, null);
canvas.drawColor(CanvasKit.Color(171, 244, 255, 0.1)); // sky blue, almost transparent
} else {
canvas.clear(CanvasKit.Color(171, 244, 255)); // sky blue, opaque
}
canvas.save();
// Set up 3D view enviroment
canvas.concat(CanvasKit.M44.setupCamera(
CanvasKit.LTRBRect(0, 0, sizeX, sizeY), halfDim, cam));
// Rotate the whole paragraph as a unit.
const paraRotPoint = [halfDim, halfDim, 1];
canvas.concat(CanvasKit.M44.multiply(
CanvasKit.M44.translated(paraRotPoint),
CanvasKit.M44.rotated([0,1,0], rotX),
CanvasKit.M44.rotated([1,0,0], rotY * 0.2),
CanvasKit.M44.translated(CanvasKit.Vector.mulScalar(paraRotPoint, -1)),
));
// Rotate every glyph in the paragraph individually.
let i = 0;
for (const letter of letters) {
canvas.save();
let r = letter['r'];
// rotate about the center of the glyph's rect.
rotationPoint = [
margin + r[rectLeft] + (r[rectRight] - r[rectLeft]) / 2,
marginTop + r[rectTop] + (r[rectBottom] - r[rectTop]) / 2,
0
];
distanceFromPointer = CanvasKit.Vector.dist(pointer, rotationPoint.slice(0, 2));
// Rotate more around the Y-axis depending on the glyph's distance from the pointer.
canvas.concat(CanvasKit.M44.multiply(
CanvasKit.M44.translated(rotationPoint),
// note that I'm rotating around the x axis first, undoing some of the rotation done to the whole
// paragraph above, where x came second. If I rotated y first, a lot of letters would end up
// upside down, which is a bit too hard to unscramble.
CanvasKit.M44.rotated([1,0,0], rotY * -0.6),
CanvasKit.M44.rotated([0,1,0], distanceFromPointer * -0.035),
CanvasKit.M44.translated(CanvasKit.Vector.mulScalar(rotationPoint, -1)),
));
canvas.drawParagraph(letter['para'], margin + r[rectLeft], marginTop + r[rectTop]);
i++;
canvas.restore();
}
canvas.restore();
lastImage = surface.makeImageSnapshot();
}
function interact(e) {
pointer = [e.offsetX, e.offsetY]
rotX = (pointer[0] - halfDim) * radPerPixel;
rotY = (pointer[1] - halfDim) * radPerPixel * -1;
surface.requestAnimationFrame(drawFrame);
};
document.getElementById('glyphgame').addEventListener('pointermove', interact);
surface.requestAnimationFrame(drawFrame);
}
function ColorSupport(CanvasKit) {
const surface = CanvasKit.MakeCanvasSurface('colorsupport', CanvasKit.ColorSpace.ADOBE_RGB);
if (!surface) {
console.error('Could not make surface');
return;
}
const canvas = surface.getCanvas();
// If the surface is correctly initialized with a higher bit depth color type,
// And chrome is compositing it into a buffer with the P3 color space,
// then the inner round rect should be distinct and less saturated than the full red background.
// Even if the monitor it is viewed on cannot accurately represent that color space.
let red = CanvasKit.Color4f(1, 0, 0, 1);
let paint = new CanvasKit.Paint();
paint.setColor(red, CanvasKit.ColorSpace.ADOBE_RGB);
canvas.drawPaint(paint);
paint.setColor(red, CanvasKit.ColorSpace.DISPLAY_P3);
canvas.drawRRect(CanvasKit.RRectXY([50, 50, 250, 250], 30, 30), paint);
paint.setColor(red, CanvasKit.ColorSpace.SRGB);
canvas.drawRRect(CanvasKit.RRectXY([100, 100, 200, 200], 30, 30), paint);
surface.flush();
surface.delete();
}
</script>
// Copyright (c) 2011 Google Inc. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
<!DOCTYPE html>
<title>CanvasKit (Skia via Web Assembly)</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
canvas, img {
border: 1px dashed #AAA;
}
</style>
<canvas id=api1 width=300 height=300></canvas>
<canvas id=api2 width=300 height=300></canvas>
<canvas id=api3 width=300 height=300></canvas>
<br>
<img id="src" src="https://cdn.skia.org/misc/test.png"
width=40 height=40 crossorigin="anonymous">
<canvas id=api4 width=300 height=300></canvas>
<canvas id=api5 width=300 height=300></canvas>
<canvas id=api6 width=300 height=300></canvas>
<script type="text/javascript" src="/build/canvaskit.js"></script>
<script type="text/javascript" charset="utf-8">
const cdn = 'https://cdn.skia.org/misc/';
const ckLoaded = CanvasKitInit({locateFile: (file) => '/build/'+file});
const loadTestImage = fetch(cdn + 'test.png').then((response) => response.arrayBuffer());
const imageEle = document.getElementById("src");
Promise.all([ckLoaded, loadTestImage, imageEle.decode()]).then((results) => {
ContextSharingExample(results[0]);
MultiCanvasExample(...results);
});
// This example shows how CanvasKit can automatically switch between multiple canvases
// with different WebGL contexts.
function MultiCanvasExample(CanvasKit, imgBytes) {
const paint = new CanvasKit.Paint();
const surfOne = CanvasKit.MakeWebGLCanvasSurface("api1");
const canvasOne = surfOne.getCanvas();
const surfTwo = CanvasKit.MakeWebGLCanvasSurface("api2");
const canvasTwo = surfTwo.getCanvas();
const surfThree = CanvasKit.MakeWebGLCanvasSurface("api3");
const canvasThree = surfThree.getCanvas();
function firstFrame() {
paint.setColor(CanvasKit.Color4f(1, 0, 0, 1)); // red
canvasOne.drawRect(CanvasKit.LTRBRect(0, 0, 300, 300), paint);
surfOne.flush();
paint.setColor(CanvasKit.Color4f(0, 1, 0, 1)); // green
canvasTwo.drawRect(CanvasKit.LTRBRect(0, 0, 300, 300), paint);
surfTwo.flush();
paint.setColor(CanvasKit.Color4f(0, 0, 1, 1)); // blue
canvasThree.drawRect(CanvasKit.LTRBRect(0, 0, 300, 300), paint);
surfThree.flush();
window.requestAnimationFrame(secondFrame);
}
let img;
function secondFrame() {
img = CanvasKit.MakeImageFromEncoded(imgBytes);
canvasOne.drawImageCubic(img, 10, 10, 0.3, 0.3, null);
surfOne.flush();
canvasTwo.drawImageCubic(img, 10, 10, 0.3, 0.3, null);
surfTwo.flush();
canvasThree.drawImageCubic(img, 10, 10, 0.3, 0.3, null);
surfThree.flush();
window.requestAnimationFrame(thirdFrame);
}
function thirdFrame() {
canvasOne.drawImageCubic(img, 100, 100, 0.3, 0.3, null);
surfOne.flush();
canvasTwo.drawImageCubic(img, 100, 100, 0.3, 0.3, null);
surfTwo.flush();
canvasThree.drawImageCubic(img, 100, 100, 0.3, 0.3, null);
surfThree.flush();
img.delete();
}
window.requestAnimationFrame(firstFrame);
}
function ContextSharingExample(CanvasKit) {
const img = CanvasKit.MakeLazyImageFromTextureSource(imageEle);
const surfOne = CanvasKit.MakeWebGLCanvasSurface("api4");
const surfTwo = CanvasKit.MakeWebGLCanvasSurface("api5");
const surfThree = CanvasKit.MakeWebGLCanvasSurface("api6");
let i = 0;
function drawFrame(canvas) {
canvas.drawImageCubic(img, 5+i, 5+i, 0.3, 0.3, null);
i += 1
if (i >= 3) {
if (i > 60) {
img.delete();
return;
}
if (i % 2) {
surfOne.requestAnimationFrame(drawFrame);
} else {
surfTwo.requestAnimationFrame(drawFrame);
}
}
}
surfOne.requestAnimationFrame(drawFrame);
surfTwo.requestAnimationFrame(drawFrame);
surfThree.requestAnimationFrame(drawFrame);
}
</script>

Sorry, the diff of this file is too big to display

Copyright 2022 WebGPU Developers
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{
"name": "@webgpu/types",
"repository": "gpuweb/types",
"homepage": "https://github.com/gpuweb/types",
"bugs": "https://github.com/gpuweb/types/issues",
"version": "0.1.21",
"main": "",
"types": "dist/index.d.ts",
"license": "BSD-3-Clause",
"files": [
"dist/**/*"
],
"scripts": {
"generate": "bikeshed-to-ts --in ./gpuweb/spec/index.bs --out ./generated/index.d.ts --forceGlobal --nominal && prettier -w generated/index.d.ts",
"format": "prettier -w dist/index.d.ts"
},
"devDependencies": {
"bikeshed-to-ts": "github:toji/bikeshed-to-ts",
"prettier": "^2.2.1"
}
}
# Typescript Type Definitions for WebGPU
This package defines Typescript types (`.d.ts`) for the upcoming [WebGPU standard](https://github.com/gpuweb/gpuweb/wiki/Implementation-Status).
_This package matches the work-in-progress WebGPU API, which is **currently unstable!**_
Use this package to augment the ambient [`"dom"`](https://www.typescriptlang.org/docs/handbook/compiler-options.html#compiler-options) type definitions with the new definitions for WebGPU.
## What are declaration files?
See the [TypeScript handbook](http://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html).
## How can I use them?
### Install
- npm: `npm install --save @webgpu/types`
- yarn: `yarn add @webgpu/types`
### Configure
#### TypeScript `tsc` and `tsc`-based bundlers
In `tsconfig.json`:
```js
{
// ...
"compilerOptions": {
// ...
"typeRoots": [ "./node_modules/@webgpu/types", "./node_modules/@types"]
}
}
```
#### Webpack
In `webpack.config.js` add:
```js
"types": ["@webgpu/types"]
```
(may not be necessary with `tsc` config above - untested)
#### Inline in TypeScript
```ts
/// <reference types="@webgpu/types" />
```
#### Others?
Please contribute a PR to add instructions for other setups or improve existing instructions. :)
## How to update these types
- Make sure the submodule is checked out: `git submodule update --init`
- Pull `gpuweb` changes: `pushd gpuweb && git checkout main && git pull && popd`
- Install dependencies: `npm ci`
- Generate `generated/index.d.ts`: `npm run generate`
- Open a diff between `generated/index.d.ts` and `dist/index.d.ts`.
The generated file is tracked by Git so you can see what has changed.
Update the latter according to changes from the former.
Note the `generated/` and `dist/` files are not the same.
See below for intentional differences.
- Format the result: `npm run format`
### Intentional differences between generator output and final result
Most or all of these should be fixed in the generator over time.
- `Array` changed to `Iterable` for WebIDL `sequence`s in argument positions.
- `any` changed to `object` for WebIDL `object`.
- `| SharedArrayBuffer` added for `[AllowShared] BufferSource`.
The following differences are TODO: should be changed in the final result.
- Deprecated items should be removed.
The following differences will remain.
- `onuncapturederror` strongly typed.
- `getContext` definitions.
- `GPUExtent3DStrict` (and similar).
### Publish a new npm package version
(only for people who have npm publish access)
* One line cmd to copy-n-paste (for ssh git user, and you'd better know what you are doing, if it failed at certain steps, you might need to clean up git tags before trying again)
- `git checkout main && git pull git@github.com:gpuweb/types.git main && git submodule update --init && npm version patch && git push git@github.com:gpuweb/types.git main --tags && npm publish`
* Separate steps (better for publishing for the first time)
* Make sure you are in the upstream repo, not your forked one. And make sure you are synced to latest commit intended for publish
- `git checkout main`
- `git pull https://github.com/gpuweb/types.git main`
- (If you are using HTTPS regularly. You can use remote names like `origin`, just make sure you are referring to the right repo)
- `git submodule update --init`
* Create the version tag and commit, and push
- `npm version patch`
- `git push https://github.com/gpuweb/types.git main --tags`
* publish the package
- `npm publish --otp=<code>`
- Replace `<code>` with the one-time password from your authenticator, since two-factors authentication is required to publish.
- If you are doing for the first time, you will do `npm adduser` first and it will guide you through adding the npm account.
const CanvasKitInit = require('./bin/canvaskit.js');
const fs = require('fs');
const path = require('path');
const assetPath = path.join(__dirname, '..', 'tests', 'assets');
CanvasKitInit({
locateFile: (file) => __dirname + '/bin/'+file,
}).then((CanvasKit) => {
let canvas = CanvasKit.MakeCanvas(300, 300);
let img = fs.readFileSync(path.join(assetPath, 'mandrill_512.png'));
img = canvas.decodeImage(img);
let fontData = fs.readFileSync(path.join(assetPath, 'Roboto-Regular.woff'));
canvas.loadFont(fontData, {
'family': 'Roboto',
'style': 'normal',
'weight': '400',
});
let ctx = canvas.getContext('2d');
ctx.font = '30px Roboto';
ctx.rotate(.1);
ctx.fillText('Awesome ', 50, 100);
ctx.strokeText('Groovy!', 250, 100);
// Draw line under Awesome
ctx.strokeStyle = 'rgba(125,0,0,0.5)';
ctx.beginPath();
ctx.lineWidth = 6;
ctx.lineTo(50, 102);
ctx.lineTo(250, 102);
ctx.stroke();
// squished vertically
ctx.globalAlpha = 0.7
ctx.imageSmoothingQuality = 'medium';
ctx.drawImage(img, 150, 150, 150, 100);
ctx.rotate(-.2);
ctx.imageSmoothingEnabled = false;
ctx.drawImage(img, 100, 150, 400, 350, 10, 200, 150, 100);
console.log('drop in Canvas2D replacement');
console.log('<img src="' + canvas.toDataURL() + '" />');
fancyAPI(CanvasKit);
});
function fancyAPI(CanvasKit) {
let surface = CanvasKit.MakeSurface(300, 300);
const canvas = surface.getCanvas();
const paint = new CanvasKit.Paint();
let robotoData = fs.readFileSync(path.join(assetPath, 'Roboto-Regular.woff'));
const roboto = CanvasKit.Typeface.MakeTypefaceFromData(robotoData);
const textPaint = new CanvasKit.Paint();
textPaint.setColor(CanvasKit.Color(40, 0, 0));
textPaint.setAntiAlias(true);
const textFont = new CanvasKit.Font(roboto, 30);
const skpath = starPath(CanvasKit);
const dpe = CanvasKit.PathEffect.MakeDash([15, 5, 5, 10], 1);
paint.setPathEffect(dpe);
paint.setStyle(CanvasKit.PaintStyle.Stroke);
paint.setStrokeWidth(5.0);
paint.setAntiAlias(true);
paint.setColor(CanvasKit.Color(66, 129, 164, 1.0));
canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
canvas.drawPath(skpath, paint);
canvas.drawText('Try Clicking!', 10, 280, textPaint, textFont);
surface.flush();
const img = surface.makeImageSnapshot();
if (!img) {
console.error('no snapshot');
return;
}
const pngBytes = img.encodeToBytes();
if (!pngBytes) {
console.error('encoding failure');
return;
}
// See https://stackoverflow.com/a/12713326
let b64encoded = Buffer.from(pngBytes).toString('base64');
console.log('Other APIs too!');
console.log(`<img src="data:image/png;base64,${b64encoded}" />`);
// These delete calls free up memeory in the C++ WASM memory block.
dpe.delete();
skpath.delete();
textPaint.delete();
paint.delete();
roboto.delete();
textFont.delete();
surface.dispose();
}
function starPath(CanvasKit, X=128, Y=128, R=116) {
let p = new CanvasKit.Path();
p.moveTo(X + R, Y);
for (let i = 1; i < 8; i++) {
let a = 2.6927937 * i;
p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a));
}
return p;
}
{
"name": "@highagency/pencil-skia",
"version": "0.48.1",
"description": "A WASM version of Skia's Canvas API",
"main": "bin/canvaskit.js",
"homepage": "https://github.com/google/skia/tree/main/modules/canvaskit",
"bugs": {
"url": "https://bugs.chromium.org/p/skia/issues/entry"
},
"publishConfig": {
"registry": "https://npm.pkg.github.com"
},
"scripts": {
"dtslint": "dtslint types"
},
"types": "./types/index.d.ts",
"license": "BSD-3-Clause",
"dependencies": {
"@webgpu/types": "0.1.21"
},
"devDependencies": {
"@definitelytyped/header-parser": "0.0.121",
"dtslint": "4.2.1",
"typescript": "4.7.4"
},
"exports": {
".": {
"require": "./bin/canvaskit.js",
"import": "./bin/canvaskit.js",
"types": "./types/index.d.ts"
},
"./full": {
"require": "./bin/full/canvaskit.js",
"import": "./bin/full/canvaskit.js",
"types": "./types/index.d.ts"
},
"./profiling": {
"require": "./bin/profiling/canvaskit.js",
"import": "./bin/profiling/canvaskit.js",
"types": "./types/index.d.ts"
},
"./bin/canvaskit": {
"require": "./bin/canvaskit.js",
"import": "./bin/canvaskit.js",
"types": "./types/index.d.ts"
},
"./bin/canvaskit.js": {
"require": "./bin/canvaskit.js",
"import": "./bin/canvaskit.js",
"types": "./types/index.d.ts"
},
"./bin/canvaskit.wasm": "./bin/canvaskit.wasm",
"./bin/full/canvaskit": {
"require": "./bin/full/canvaskit.js",
"import": "./bin/full/canvaskit.js",
"types": "./types/index.d.ts"
},
"./bin/full/canvaskit.js": {
"require": "./bin/full/canvaskit.js",
"import": "./bin/full/canvaskit.js",
"types": "./types/index.d.ts"
},
"./bin/full/canvaskit.wasm": "./bin/full/canvaskit.wasm",
"./bin/profiling/canvaskit": {
"require": "./bin/profiling/canvaskit.js",
"import": "./bin/profiling/canvaskit.js",
"types": "./types/index.d.ts"
},
"./bin/profiling/canvaskit.js": {
"require": "./bin/profiling/canvaskit.js",
"import": "./bin/profiling/canvaskit.js",
"types": "./types/index.d.ts"
},
"./bin/profiling/canvaskit.wasm": "./bin/profiling/canvaskit.wasm",
"./package.json": "./package.json"
}
}
<!DOCTYPE html>
<title>CanvasKit Paragraph (with & without ICU)</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script type="text/javascript" src="/build/canvaskit.js"></script>
<style>
canvas {
border: 1px dashed #AAA;
}
#withICU {
border-color: red;
}
#withoutICU {
border-color: green;
}
#sampleText {
width: 400px;
height: 200px;
}
</style>
<table>
<thead>
<th><h2 style="color: red;">With ICU</h2></th>
<th></th>
<th><h2 style="color: green;">Without ICU</h2></th>
</thead>
<tr>
<td><canvas id="withICU" width=600 height=600></canvas></td>
<td style="width: 20px;"></td>
<td><canvas id="withoutICU" width=600 height=600 tabindex='-1'></canvas></td>
</tr>
</table>
<textarea id="sampleText">The لاquick 😠(brown) fox
واحد (اثنان) ثلاثة
ate a hamburger.
</textarea>
<script type="text/javascript" charset="utf-8">
var CanvasKit = null;
var fonts = null;
var sampleText = null;
var cdn = 'https://cdn.skia.org/misc/';
const ckLoaded = CanvasKitInit({locateFile: (file) => '/build/'+file});
const loadFonts = [
fetch(cdn + 'Roboto-Regular.ttf').then((response) => response.arrayBuffer()),
fetch('https://fonts.gstatic.com/s/notoemoji/v26/bMrnmSyK7YY-MEu6aWjPDs-ar6uWaGWuob-r0jwvS-FGJCMY.ttf').then((response) => response.arrayBuffer()),
fetch('https://fonts.gstatic.com/s/notosansarabic/v18/nwpxtLGrOAZMl5nJ_wfgRg3DrWFZWsnVBJ_sS6tlqHHFlhQ5l3sQWIHPqzCfyGyvu3CBFQLaig.ttf').then((response) => response.arrayBuffer()),
];
let paragraphWithICU;
let paragraphWithoutICU;
Promise.all([ckLoaded, ...loadFonts]).then(([_CanvasKit, ..._fonts]) => {
CanvasKit = _CanvasKit;
fonts = _fonts;
const textarea = document.getElementById('sampleText');
sampleText = textarea.value;
textarea.addEventListener('input', (e) => {
sampleText = e.target.value;
paragraphWithICU = ParagraphWithICU();
paragraphWithoutICU = ParagraphWithoutICU();
});
paragraphWithICU = ParagraphWithICU();
paragraphWithoutICU = ParagraphWithoutICU();
continuousRendering('withICU', () => paragraphWithICU);
continuousRendering('withoutICU', () => paragraphWithoutICU);
});
const fontFamilies = [
'Roboto',
'Noto Emoji',
'Noto Sans Arabic',
];
function continuousRendering(elementId, getParagraph) {
const surface = CanvasKit.MakeCanvasSurface(elementId);
if (!surface) {
throw new Error('Could not make surface');
}
function drawFrame(canvas) {
drawParagraph(canvas, getParagraph());
surface.requestAnimationFrame(drawFrame);
}
surface.requestAnimationFrame(drawFrame);
}
function ParagraphWithICU() {
if (!CanvasKit || !fonts) {
throw new Error('CanvasKit or fonts not loaded');
}
const fontMgr = CanvasKit.FontMgr.FromData(fonts);
const paraStyle = new CanvasKit.ParagraphStyle({
textStyle: {
color: CanvasKit.BLACK,
fontFamilies: fontFamilies,
fontSize: 50,
},
textAlign: CanvasKit.TextAlign.Left,
maxLines: 4,
});
const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
builder.addText(sampleText);
const paragraph = builder.build();
fontMgr.delete();
return paragraph;
}
function ParagraphWithoutICU() {
if (!CanvasKit || !fonts) {
throw new Error('CanvasKit or fonts not loaded');
}
const fontMgr = CanvasKit.FontMgr.FromData(fonts);
const paraStyle = new CanvasKit.ParagraphStyle({
textStyle: {
color: CanvasKit.BLACK,
fontFamilies: fontFamilies,
fontSize: 50,
},
maxLines: 4,
textAlign: CanvasKit.TextAlign.Left,
});
const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
builder.addText(sampleText);
const text = sampleText;
// Pass the entire text as one word. It's only used for the method
// getWords.
const mallocedWords = CanvasKit.Malloc(Uint32Array, 2);
mallocedWords.toTypedArray().set([0, text.length]);
const graphemeBoundaries = getGraphemeBoundaries(text);
const mallocedGraphemes = CanvasKit.Malloc(Uint32Array, graphemeBoundaries.length);
mallocedGraphemes.toTypedArray().set(graphemeBoundaries);
const lineBreaks = getLineBreaks(text);
const mallocedLineBreaks = CanvasKit.Malloc(Uint32Array, lineBreaks.length);
mallocedLineBreaks.toTypedArray().set(lineBreaks);
console.log('RequiresClientICU:', CanvasKit.ParagraphBuilder.RequiresClientICU());
builder.setWordsUtf16(mallocedWords);
builder.setGraphemeBreaksUtf16(mallocedGraphemes);
builder.setLineBreaksUtf16(mallocedLineBreaks);
const paragraph = builder.build();
fontMgr.delete();
return paragraph;
}
function drawParagraph(canvas, paragraph) {
const fontPaint = new CanvasKit.Paint();
fontPaint.setStyle(CanvasKit.PaintStyle.Fill);
fontPaint.setAntiAlias(true);
canvas.clear(CanvasKit.WHITE);
const wrapTo = 350 + 150 * Math.sin(Date.now() / 4000);
paragraph.layout(wrapTo);
const rects = [
...paragraph.getRectsForRange(2, 8, CanvasKit.RectHeightStyle.Tight, CanvasKit.RectWidthStyle.Tight),
...paragraph.getRectsForRange(12, 16, CanvasKit.RectHeightStyle.Tight, CanvasKit.RectWidthStyle.Tight),
];
const rectPaint = new CanvasKit.Paint();
const colors = [CanvasKit.CYAN, CanvasKit.MAGENTA, CanvasKit.BLUE, CanvasKit.YELLOW];
for (const rect of rects) {
rectPaint.setColor(colors.shift() || CanvasKit.RED);
canvas.drawRect(rect, rectPaint);
}
canvas.drawParagraph(paragraph, 0, 0);
canvas.drawLine(wrapTo, 0, wrapTo, 400, fontPaint);
}
const SOFT = 0;
const HARD = 1;
function getLineBreaks(text) {
const breaks = [0, SOFT];
const iterator = new Intl.v8BreakIterator(['en'], {type: 'line'});
iterator.adoptText(text);
iterator.first();
while (iterator.next() != -1) {
breaks.push(iterator.current(), getBreakType(iterator.breakType()));
}
return breaks;
}
function getBreakType(v8BreakType) {
return v8BreakType == 'none' ? SOFT : HARD;
}
function getGraphemeBoundaries(text) {
const segmenter = new Intl.Segmenter(['en'], {type: 'grapheme'});
const segments = segmenter.segment(text);
const graphemeBoundaries = [];
for (const segment of segments) {
graphemeBoundaries.push(segment.index);
}
graphemeBoundaries.push(text.length);
return graphemeBoundaries;
}
</script>
A WASM version of Skia's Canvas API.
See https://skia.org/user/modules/canvaskit for more background information.
# Getting Started
## Browser
To use the library, run `npm install canvaskit-wasm` and then simply include it:
```html
<script src="/node_modules/canvaskit-wasm/bin/canvaskit.js"></script>
```
```javascript
CanvasKitInit({
locateFile: (file) => '/node_modules/canvaskit-wasm/bin/'+file,
}).then((CanvasKit) => {
// Code goes here using CanvasKit
});
```
As with all npm packages, there's a freely available CDN via unpkg.com:
```html
<script src="https://unpkg.com/canvaskit-wasm@latest/bin/canvaskit.js"></script>
```
```javascript
CanvasKitInit({
locateFile: (file) => 'https://unpkg.com/canvaskit-wasm@latest/bin/'+file,
}).then((CanvasKit) => {
// Code goes here using CanvasKit
});
```
## Node
To use CanvasKit in Node, it's similar to the browser:
```javascript
const CanvasKitInit = require('canvaskit-wasm/bin/canvaskit.js');
CanvasKitInit({
locateFile: (file) => __dirname + '/bin/'+file,
}).then((CanvasKit) => {
// Code goes here using CanvasKit
});
```
## WebPack
WebPack's support for WASM is still somewhat experimental, but CanvasKit can be
used with a few configuration changes.
In the JS code, use require():
```javascript
const CanvasKitInit = require('canvaskit-wasm/bin/canvaskit.js')
CanvasKitInit().then((CanvasKit) => {
// Code goes here using CanvasKit
});
```
Since WebPack does not expose the entire `/node_modules/` directory, but instead
packages only the needed pieces, we have to copy canvaskit.wasm into the build directory.
One such solution is to use [CopyWebpackPlugin](https://github.com/webpack-contrib/copy-webpack-plugin).
For example, add the following plugin:
```javascript
config.plugins.push(
new CopyWebpackPlugin([
{ from: 'node_modules/canvaskit-wasm/bin/canvaskit.wasm' }
])
);
```
If webpack gives an error similar to:
```warn
ERROR in ./node_modules/canvaskit-wasm/bin/canvaskit.js
Module not found: Error: Can't resolve 'fs' in '...'
```
Then, add the following configuration change to the node section of the config:
```javascript
config.node = {
fs: 'empty'
};
```
# Different canvaskit bundles
`canvaskit-wasm` includes 3 types of bundles:
* default `./bin/canvaskit.js` - Basic canvaskit functionality
```javascript
const InitCanvasKit = require('canvaskit-wasm/bin/canvaskit');
```
* full `./bin/full/canvaskit.js` - includes [Skottie](https://skia.org/docs/user/modules/skottie/) and other libraries
```javascript
const InitCanvasKit = require('canvaskit-wasm/bin/full/canvaskit');
```
* profiling `./bin/profiling/canvaskit.js` - the same as `full` but contains full names of wasm functions called internally
```javascript
const InitCanvasKit = require('canvaskit-wasm/bin/profiling/canvaskit');
```
# ES6 import and node entrypoints
This package also exposes [entrypoints](https://nodejs.org/api/packages.html#package-entry-points)
```javascript
import InitCanvasKit from 'canvaskit-wasm'; // default
```
```javascript
import InitCanvasKit from 'canvaskit-wasm/full';
```
```javascript
import InitCanvasKit from 'canvaskit-wasm/profiling';
```
If you use [typescript](https://www.typescriptlang.org/)
you need to enable [resolvePackageJsonExports](https://www.typescriptlang.org/tsconfig#resolvePackageJsonExports) in your `tsconfig.json`
```json
{
"compilerOptions": {
"resolvePackageJsonExports": true
}
}
```
# Using the CanvasKit API
See `example.html` and `node.example.js` for demos of how to use the core API.
See `extra.html` for some optional add-ins like an animation player (Skottie).
See `types/index.d.ts` for a typescript definition file that contains all the
APIs and some documentation about them.
## Drop-in Canvas2D replacement
For environments where an HTML canvas is not available (e.g. Node, headless servers),
CanvasKit has an optional API (included by default) that mostly mirrors the [CanvasRenderingContext2D](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D).
```javascript
const skcanvas = CanvasKit.MakeCanvas(600, 600);
const ctx = skcanvas.getContext('2d');
const rgradient = ctx.createRadialGradient(200, 300, 10, 100, 100, 300);
// Add three color stops
rgradient.addColorStop(0, 'red');
rgradient.addColorStop(0.7, 'white');
rgradient.addColorStop(1, 'blue');
ctx.fillStyle = rgradient;
ctx.globalAlpha = 0.7;
ctx.fillRect(0, 0, 600, 600);
const imgData = skcanvas.toDataURL();
// imgData is now a base64 encoded image.
```
See more examples in `example.html` and `node.example.js`.
### Known issues with Canvas2D Emulation layer
- measureText returns width only and does no shaping. It is only sort of valid with ASCII letters.
- textAlign is not supported.
- textBaseAlign is not supported.
- fillText does not support the width parameter.
# Filing bugs
Please file bugs at [https://skbug.com](skbug.com).
It may be convenient to use [our online fiddle](https://jsfiddle.skia.org/canvaskit) to demonstrate any issues encountered.
See CONTRIBUTING.md for more information on sending pull requests.
# Types and Documentation
There are Typescript types and associated API docs in [types/](./types/).
<!DOCTYPE html>
<title>WIP Shaping in JS Demo</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
canvas {
border: 1px dashed #AAA;
}
#input {
height: 300px;
}
</style>
<h2> (Really Bad) Shaping in JS </h2>
<textarea id=input></textarea>
<canvas id=shaped_text width=300 height=300></canvas>
<script type="text/javascript" src="/build/canvaskit.js"></script>
<script type="text/javascript" charset="utf-8">
let CanvasKit = null;
const cdn = 'https://cdn.skia.org/misc/';
const ckLoaded = CanvasKitInit({locateFile: (file) => '/build/'+file});
const loadFont = fetch(cdn + 'Roboto-Regular.ttf').then((response) => response.arrayBuffer());
// This font works with interobang.
//const loadFont = fetch('https://cdn.skia.org/google-web-fonts/SourceSansPro-Regular.ttf').then((response) => response.arrayBuffer());
document.getElementById('input').value = 'An aegis protected the fox!?';
// Examples requiring external resources.
Promise.all([ckLoaded, loadFont]).then((results) => {
ShapingJS(...results);
});
function ShapingJS(CanvasKit, fontData) {
if (!CanvasKit || !fontData) {
return;
}
const surface = CanvasKit.MakeCanvasSurface('shaped_text');
if (!surface) {
console.error('Could not make surface');
return;
}
const typeface = CanvasKit.Typeface.MakeTypefaceFromData(fontData);
const paint = new CanvasKit.Paint();
paint.setColor(CanvasKit.BLUE);
paint.setStyle(CanvasKit.PaintStyle.Stroke);
const textPaint = new CanvasKit.Paint();
const textFont = new CanvasKit.Font(typeface, 20);
textFont.setLinearMetrics(true);
textFont.setSubpixel(true);
textFont.setHinting(CanvasKit.FontHinting.Slight);
// Only care about these characters for now. If we get any unknown characters, we'll replace
// them with the first glyph here (the replacement glyph).
// We put the family code point second to make sure we handle >16 bit codes correctly.
const alphabet = "�👪abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 _.,?!æ‽";
const ids = textFont.getGlyphIDs(alphabet);
const unknownCharacterGlyphID = ids[0];
// char here means "string version of unicode code point". This makes the code below a bit more
// readable than just integers. We just have to take care when reading these in that we don't
// grab the second half of a 32 bit code unit.
const charsToGlyphIDs = {};
// Indexes in JS correspond to a 16 bit or 32 bit code unit. If a code point is wider than
// 16 bits, it overflows into the next index. codePointAt will return a >16 bit value if the
// given index overflows. We need to check for this and skip the next index lest we get a
// garbage value (the second half of the Unicode code point.
let glyphIdx = 0;
for (let i = 0; i < alphabet.length; i++) {
charsToGlyphIDs[alphabet[i]] = ids[glyphIdx];
if (alphabet.codePointAt(i) > 65535) {
i++; // skip the next index because that will be the second half of the code point.
}
glyphIdx++;
}
// TODO(kjlubick): linear metrics so we get "correct" data (e.g. floats).
const bounds = textFont.getGlyphBounds(ids, textPaint);
const widths = textFont.getGlyphWidths(ids, textPaint);
// See https://www.freetype.org/freetype2/docs/glyphs/glyphs-3.html
// Note that in Skia, y-down is positive, so it is common to see yMax below be negative.
const glyphMetricsByGlyphID = {};
for (let i = 0; i < ids.length; i++) {
glyphMetricsByGlyphID[ids[i]] = {
xMin: bounds[i*4],
yMax: bounds[i*4 + 1],
xMax: bounds[i*4 + 2],
yMin: bounds[i*4 + 3],
xAdvance: widths[i],
};
}
const shapeAndDrawText = (str, canvas, x, y, maxWidth, font, paint) => {
const LINE_SPACING = 20;
// This is a conservative estimate - it can be shorter if we have ligatures code points
// that span multiple 16bit words.
const glyphs = CanvasKit.MallocGlyphIDs(str.length);
let glyphArr = glyphs.toTypedArray();
// Turn the code points into glyphs, accounting for up to 2 ligatures.
let shapedGlyphIdx = -1;
for (let i = 0; i < str.length; i++) {
const char = str[i];
shapedGlyphIdx++;
// POC Ligature support.
if (charsToGlyphIDs['æ'] && char === 'a' && str[i+1] === 'e') {
glyphArr[shapedGlyphIdx] = charsToGlyphIDs['æ'];
i++; // skip next code point
continue;
}
if (charsToGlyphIDs['‽'] && (
(char === '?' && str[i+1] === '!') || (char === '!' && str[i+1] === '?' ))) {
glyphArr[shapedGlyphIdx] = charsToGlyphIDs['‽'];
i++; // skip next code point
continue;
}
glyphArr[shapedGlyphIdx] = charsToGlyphIDs[char] || unknownCharacterGlyphID;
if (str.codePointAt(i) > 65535) {
i++; // skip the next index because that will be the second half of the code point.
}
}
// Trim down our array of glyphs to only the amount we have after ligatures and code points
// that are > 16 bits.
glyphArr = glyphs.subarray(0, shapedGlyphIdx+1);
// Break our glyphs into runs based on the maxWidth and the xAdvance.
const glyphRuns = [];
let currentRunStartIdx = 0;
let currentWidth = 0;
for (let i = 0; i < glyphArr.length; i++) {
const nextGlyphWidth = glyphMetricsByGlyphID[glyphArr[i]].xAdvance;
if (currentWidth + nextGlyphWidth > maxWidth) {
glyphRuns.push(glyphs.subarray(currentRunStartIdx, i));
currentRunStartIdx = i;
currentWidth = 0;
}
currentWidth += nextGlyphWidth;
}
glyphRuns.push(glyphs.subarray(currentRunStartIdx, glyphArr.length));
// Draw all those runs.
for (let i = 0; i < glyphRuns.length; i++) {
const blob = CanvasKit.TextBlob.MakeFromGlyphs(glyphRuns[i], font);
if (blob) {
canvas.drawTextBlob(blob, x, y + LINE_SPACING*i, paint);
}
blob.delete();
}
CanvasKit.Free(glyphs);
}
const drawFrame = (canvas) => {
canvas.clear(CanvasKit.WHITE);
canvas.drawText('a + e = ae (no ligature)',
5, 30, textPaint, textFont);
canvas.drawText('a + e = æ (hard-coded ligature)',
5, 50, textPaint, textFont);
canvas.drawRect(CanvasKit.LTRBRect(10, 80, 280, 290), paint);
shapeAndDrawText(document.getElementById('input').value, canvas, 15, 100, 265, textFont, textPaint);
surface.requestAnimationFrame(drawFrame)
};
surface.requestAnimationFrame(drawFrame);
}
</script>
function ASSERT(pred) {
console.assert(pred, 'assert failed');
}
function LOG(...args) {
// comment out for non-debugging
// console.log(args);
}
function MakeCursor(CanvasKit) {
const linePaint = new CanvasKit.Paint();
linePaint.setColor([0,0,1,1]);
linePaint.setStyle(CanvasKit.PaintStyle.Stroke);
linePaint.setStrokeWidth(2);
linePaint.setAntiAlias(true);
const pathPaint = new CanvasKit.Paint();
pathPaint.setColor([0,0,1,0.25]);
linePaint.setAntiAlias(true);
return {
_line_paint: linePaint, // wrap in weak-ref so we can delete it?
_path_paint: pathPaint,
_x: 0,
_top: 0,
_bottom: 0,
_path: null, // only use x,top,bottom if path is null
_draws_per_sec: 2,
// pass 0 for no-draw, pass inf. for always on
setBlinkRate: function(blinks_per_sec) {
this._draws_per_sec = blinks_per_sec;
},
place: function(x, top, bottom) {
this._x = x;
this._top = top;
this._bottom = bottom;
this._path = null;
},
setPath: function(path) {
this._path = path;
},
draw_before: function(canvas) {
if (this._path) {
canvas.drawPath(this._path, this._path_paint);
}
},
draw_after: function(canvas) {
if (this._path) {
return;
}
if (Math.floor(Date.now() * this._draws_per_sec / 1000) & 1) {
canvas.drawLine(this._x, this._top, this._x, this._bottom, this._line_paint);
}
},
};
}
function MakeMouse() {
return {
_start_x: 0, _start_y: 0,
_curr_x: 0, _curr_y: 0,
_active: false,
isActive: function() {
return this._active;
},
setDown: function(x, y) {
this._start_x = this._curr_x = x;
this._start_y = this._curr_y = y;
this._active = true;
},
setMove: function(x, y) {
this._curr_x = x;
this._curr_y = y;
},
setUp: function(x, y) {
this._curr_x = x;
this._curr_y = y;
this._active = false;
},
getPos: function(dx, dy) {
return [ this._start_x + dx, this._start_y + dy, this._curr_x + dx, this._curr_y + dy ];
},
};
}
function runs_x_to_index(runs, x) {
for (const r of runs) {
for (let i = 1; i < r.offsets.length; i += 1) {
if (x < r.positions[i*2]) {
const mid = (r.positions[i*2-2] + r.positions[i*2]) * 0.5;
if (x <= mid) {
return r.offsets[i-1];
} else {
return r.offsets[i];
}
}
}
}
const r = runs[runs.length-1];
return r.offsets[r.offsets.length-1];
}
function lines_pos_to_index(lines, x, y) {
if (y < lines[0].top) {
return 0;
}
for (const l of lines) {
if (y <= l.bottom) {
return runs_x_to_index(l.runs, x);
}
}
return lines[lines.length - 1].textRange.last;
}
function runs_index_to_run(runs, index) {
for (const r of runs) {
if (index <= r.offsets[r.offsets.length-1]) {
return r;
}
}
return runs[runs.length-1]; // last run
}
function runs_index_to_x(runs, index) {
const r = runs_index_to_run(runs, index);
for (const i in r.offsets) {
if (index == r.offsets[i]) {
return r.positions[i*2];
}
}
return r.positions[r.positions.length-2]; // last x
}
function lines_index_to_line_index(lines, index) {
let i = 0;
for (const l of lines) {
if (index <= l.textRange.last) {
return i;
}
i += 1;
}
return lines.length-1;
}
function lines_index_to_line(lines, index) {
return lines[lines_index_to_line_index(lines, index)];
}
function lines_index_to_x(lines, index) {
for (const l of lines) {
if (index <= l.textRange.last) {
return runs_index_to_x(l.runs, index);
}
}
}
function lines_indices_to_path(lines, a, b, width) {
if (a == b) {
return null;
}
if (a > b) { [a, b] = [b, a]; }
const path = new CanvasKit.Path();
const la = lines_index_to_line(lines, a);
const lb = lines_index_to_line(lines, b);
const ax = runs_index_to_x(la.runs, a);
const bx = runs_index_to_x(lb.runs, b);
if (la == lb) {
path.addRect([ax, la.top, bx, la.bottom]);
} else {
path.addRect([ax, la.top, width, la.bottom]);
path.addRect([0, lb.top, bx, lb.bottom]);
if (la.bottom < lb.top) {
path.addRect([0, la.bottom, width, lb.top]); // extra lines inbetween
}
}
return path;
}
function string_del(str, start, end) {
return str.slice(0, start) + str.slice(end, str.length);
}
function make_default_paint() {
const p = new CanvasKit.Paint();
p.setAntiAlias(true);
return p;
}
function make_default_font(tf) {
const font = new CanvasKit.Font(tf);
font.setSubpixel(true);
return font;
}
function MakeStyle(length) {
return {
_length: length,
typeface: null,
size: null,
color: null,
bold: null,
italic: null,
underline: null,
_check_toggle: function(src, dst) {
if (src == 'toggle') {
return !dst;
} else {
return src;
}
},
// returns true if we changed something affecting layout
mergeFrom: function(src) {
let layoutChanged = false;
if (src.typeface && this.typeface !== src.typeface) {
this.typeface = src.typeface;
layoutChanged = true;
}
if (src.size && this.size !== src.size) {
this.size = src.size;
layoutChanged = true;
}
if (src.color) { this.color = src.color; }
if (src.bold) {
this.bold = this._check_toggle(src.bold, this.bold);
}
if (src.italic) {
this.italic = this._check_toggle(src.italic, this.italic);
}
if (src.underline) {
this.underline = this._check_toggle(src.underline, this.underline);
}
if (src.size_add) {
this.size += src.size_add;
layoutChanged = true;
}
return layoutChanged;
}
};
}
function MakeEditor(text, style, cursor, width) {
const ed = {
_text: text,
_lines: null,
_cursor: cursor,
_width: width,
_index: { start: 0, end: 0 },
_styles: null,
// drawing
_X: 0,
_Y: 0,
_paint: make_default_paint(),
_font: make_default_font(style.typeface),
getLines: function() { return this._lines; },
width: function() {
return this._width;
},
height: function() {
return this._lines[this._lines.length-1].bottom;
},
bounds: function() {
return [this._X, this._Y, this._X + this.width(), this._Y + this.height()];
},
setXY: function(x, y) {
this._X = x;
this._Y = y;
},
_rebuild_selection: function() {
const a = this._index.start;
const b = this._index.end;
ASSERT(a >= 0 && a <= b && b <= this._text.length);
if (a === b) {
const l = lines_index_to_line(this._lines, a);
const x = runs_index_to_x(l.runs, a);
this._cursor.place(x, l.top, l.bottom);
} else {
this._cursor.setPath(lines_indices_to_path(this._lines, a, b, this._width));
}
},
setIndex: function(i) {
this._index.start = this._index.end = i;
this._rebuild_selection();
},
setIndices: function(a, b) {
if (a > b) { [a, b] = [b, a]; }
this._index.start = a;
this._index.end = b;
this._rebuild_selection();
},
moveDX: function(dx) {
let index;
if (this._index.start == this._index.end) {
// just adjust and pin
index = Math.max(Math.min(this._index.start + dx, this._text.length), 0);
} else {
// 'deselect' the region, and turn it into just a single index
index = dx < 0 ? this._index.start : this._index.end;
}
this.setIndex(index);
},
moveDY: function(dy) {
let index = (dy < 0) ? this._index.start : this._index.end;
const i = lines_index_to_line_index(this._lines, index);
if (dy < 0 && i == 0) {
index = 0;
} else if (dy > 0 && i == this._lines.length - 1) {
index = this._text.length;
} else {
const x = runs_index_to_x(this._lines[i].runs, index);
// todo: statefully track "original" x when an up/down sequence started,
// so we can avoid drift.
index = runs_x_to_index(this._lines[i+dy].runs, x);
}
this.setIndex(index);
},
_validateStyles: function() {
let len = 0;
for (const s of this._styles) {
len += s._length;
}
ASSERT(len === this._text.length);
},
_validateBlocks: function(blocks) {
let len = 0;
for (const b of blocks) {
len += b.length;
}
ASSERT(len === this._text.length);
},
_buildLines: function() {
this._validateStyles();
const build_sparse = true;
const blocks = [];
let block = null;
for (const s of this._styles) {
if (build_sparse) {
if (!block || (block.typeface === s.typeface && block.size === s.size)) {
if (!block) {
block = { length: 0, typeface: s.typeface, size: s.size };
}
block.length += s._length;
} else {
blocks.push(block);
block = { length: s._length, typeface: s.typeface, size: s.size };
}
} else {
// force a block on every style boundary for now
blocks.push({ length: s._length, typeface: s.typeface, size: s.size });
}
}
if (build_sparse) {
blocks.push(block);
}
this._validateBlocks(blocks);
this._lines = CanvasKit.ParagraphBuilder.ShapeText(this._text, blocks, this._width);
this._rebuild_selection();
// add textRange to each run, to aid in drawing
this._runs = [];
for (const l of this._lines) {
for (const r of l.runs) {
r.textRange = { start: r.offsets[0], end: r.offsets[r.offsets.length-1] };
this._runs.push(r);
}
}
},
// note: this does not rebuild lines/runs, or update the cursor,
// but it does edit the text and styles
// returns true if it deleted anything
_deleteRange: function(start, end) {
ASSERT(start >= 0 && end <= this._text.length);
ASSERT(start <= end);
if (start === end) {
return false;
}
this._delete_style_range(start, end);
// Do this after shrink styles (we use text.length in an assert)
this._text = string_del(this._text, start, end);
},
deleteSelection: function() {
let start = this._index.start;
if (start == this._index.end) {
if (start == 0) {
return; // nothing to do
}
this._deleteRange(start - 1, start);
start -= 1;
} else {
this._deleteRange(start, this._index.end);
}
this._index.start = this._index.end = start;
this._buildLines();
},
insert: function(charcode) {
if (this._index.start != this._index.end) {
this.deleteSelection();
}
const index = this._index.start;
// do this before edit the text (we use text.length in an assert)
const [i, prev_len] = this.find_style_index_and_prev_length(index);
this._styles[i]._length += 1;
// now grow the text
this._text = this._text.slice(0, index) + charcode + this._text.slice(index);
this._index.start = this._index.end = index + 1;
this._buildLines();
},
draw: function(canvas) {
canvas.save();
canvas.translate(this._X, this._Y);
this._cursor.draw_before(canvas);
const runs = this._runs;
const styles = this._styles;
const f = this._font;
const p = this._paint;
let s = styles[0];
let sindex = 0;
let s_start = 0;
let s_end = s._length;
let r = runs[0];
let rindex = 0;
let start = 0;
let end = 0;
while (start < this._text.length) {
while (r.textRange.end <= start) {
r = runs[++rindex];
if (!r) {
// ran out of runs, so the remaining text must just be WS
break;
}
}
if (!r) break;
while (s_end <= start) {
s = styles[++sindex];
s_start = s_end;
s_end += s._length;
}
end = Math.min(r.textRange.end, s_end);
LOG('New range: ', start, end,
'from run', r.textRange.start, r.textRange.end,
'style', s_start, s_end);
// check that we have anything to draw
if (r.textRange.start >= end) {
start = end;
continue; // could be a span of WS with no glyphs
}
// f.setTypeface(r.typeface); // r.typeface is always null (for now)
f.setSize(r.size);
f.setEmbolden(s.bold);
f.setSkewX(s.italic ? -0.2 : 0);
p.setColor(s.color ? s.color : [0,0,0,1]);
let gly = r.glyphs;
let pos = r.positions;
if (start > r.textRange.start || end < r.textRange.end) {
// search for the subset of glyphs to draw
let glyph_start, glyph_end;
for (let i = 0; i < r.offsets.length; ++i) {
if (r.offsets[i] >= start) {
glyph_start = i;
break;
}
}
for (let i = glyph_start+1; i < r.offsets.length; ++i) {
if (r.offsets[i] >= end) {
glyph_end = i;
break;
}
}
LOG(' glyph subrange', glyph_start, glyph_end);
gly = gly.slice(glyph_start, glyph_end);
// +2 at the end so we can see the trailing position (esp. for underlines)
pos = pos.slice(glyph_start*2, glyph_end*2 + 2);
} else {
LOG(' use entire glyph run');
}
canvas.drawGlyphs(gly, pos, 0, 0, f, p);
if (s.underline) {
const gap = 2;
const Y = pos[1]; // first Y
const lastX = pos[gly.length*2];
const sects = f.getGlyphIntercepts(gly, pos, Y+2, Y+4);
let x = pos[0];
for (let i = 0; i < sects.length; i += 2) {
const end = sects[i] - gap;
if (x < end) {
canvas.drawRect([x, Y+2, end, Y+4], p);
}
x = sects[i+1] + gap;
}
if (x < lastX) {
canvas.drawRect([x, Y+2, lastX, Y+4], p);
}
}
start = end;
}
this._cursor.draw_after(canvas);
canvas.restore();
},
// Styling
// returns [index, prev total length before this style]
find_style_index_and_prev_length: function(index) {
let len = 0;
for (let i = 0; i < this._styles.length; ++i) {
const l = this._styles[i]._length;
len += l;
// < favors the latter style if index is between two styles
if (index < len) {
return [i, len - l];
}
}
ASSERT(len === this._text.length);
return [this._styles.length-1, len];
},
_delete_style_range: function(start, end) {
// shrink/remove styles
//
// [.....][....][....][.....] styles
// [..................] start...end
//
// - trim the first style
// - remove the middle styles
// - trim the last style
let N = end - start;
let [i, prev_len] = this.find_style_index_and_prev_length(start);
let s = this._styles[i];
if (start > prev_len) {
// we overlap the first style (but not entirely
const skip = start - prev_len;
ASSERT(skip < s._length);
const shrink = Math.min(N, s._length - skip);
ASSERT(shrink > 0);
s._length -= shrink;
N -= shrink;
if (N === 0) {
return;
}
i += 1;
ASSERT(i < this._styles.length);
}
while (N > 0) {
s = this._styles[i];
if (N >= s._length) {
N -= s._length;
this._styles.splice(i, 1);
} else {
s._length -= N;
break;
}
}
},
applyStyleToRange: function(style, start, end) {
if (start > end) { [start, end] = [end, start]; }
ASSERT(start >= 0 && end <= this._text.length);
if (start === end) {
return;
}
LOG('trying to apply', style, start, end);
let i;
for (i = 0; i < this._styles.length; ++i) {
if (start <= this._styles[i]._length) {
break;
}
start -= this._styles[i]._length;
end -= this._styles[i]._length;
}
let s = this._styles[i];
// do we need to fission off a clean subset for the head of s?
if (start > 0) {
const ns = Object.assign({}, s);
s._length = start;
ns._length -= start;
LOG('initial splice', i, start, s._length, ns._length);
i += 1;
this._styles.splice(i, 0, ns);
end -= start;
// we don't use start any more
}
// merge into any/all whole styles we overlap
let layoutChanged = false;
while (end >= this._styles[i]._length) {
LOG('whole run merging for style index', i)
layoutChanged |= this._styles[i].mergeFrom(style);
end -= this._styles[i]._length;
i += 1;
if (end == 0) {
break;
}
}
// do we partially cover the last run
if (end > 0) {
s = this._styles[i];
const ns = Object.assign({}, s); // the new first half
ns._length = end;
s._length -= end; // trim the (unchanged) tail
LOG('merging tail', i, ns._length, s._length);
layoutChanged |= ns.mergeFrom(style);
this._styles.splice(i, 0, ns);
}
this._validateStyles();
LOG('after applying styles', this._styles);
if (layoutChanged) {
this._buildLines();
}
},
applyStyleToSelection: function(style) {
this.applyStyleToRange(style, this._index.start, this._index.end);
},
};
const s = MakeStyle(ed._text.length);
s.mergeFrom(style);
ed._styles = [ s ];
ed._buildLines();
return ed;
}
// This file is type-checked by the Typescript definitions. It is not actually executed.
// Test it by running `npm run dtslint` in the parent directory.
import CanvasKitInit, {
AnimatedImage,
Canvas,
CanvasKit,
ColorFilter,
Font,
FontMgr,
Image,
ImageFilter,
ImageInfo, InputBidiRegions,
MaskFilter,
Paint,
Paragraph,
Path,
PathEffect,
Shader,
SkPicture,
TextBlob,
Typeface,
Vertices,
WebGPUDeviceContext,
} from "canvaskit-wasm";
CanvasKitInit({locateFile: (file: string) => '/node_modules/canvaskit/bin/' + file}).then((CK: CanvasKit) => {
animatedImageTests(CK);
canvasTests(CK);
canvas2DTests(CK);
colorFilterTests(CK);
colorTests(CK);
contourMeasureTests(CK);
imageFilterTests(CK);
imageTests(CK);
fontTests(CK);
fontMgrTests(CK);
globalTests(CK);
mallocTests(CK);
maskFilterTests(CK);
matrixTests(CK);
paintTests(CK);
paragraphTests(CK);
paragraphBuilderTests(CK);
pathEffectTests(CK);
pathTests(CK);
pictureTests(CK);
rectangleTests(CK);
runtimeEffectTests(CK);
skottieTests(CK);
shaderTests(CK);
surfaceTests(CK);
textBlobTests(CK);
typefaceTests(CK);
vectorTests(CK);
verticesTests(CK);
webGPUTest(CK);
});
function animatedImageTests(CK: CanvasKit) {
const buff = new ArrayBuffer(10);
const img = CK.MakeAnimatedImageFromEncoded(buff); // $ExpectType AnimatedImage | null
if (!img) return;
const n = img.decodeNextFrame(); // $ExpectType number
const f = img.getFrameCount(); // $ExpectType number
const r = img.getRepetitionCount(); // $ExpectType number
const h = img.height(); // $ExpectType number
const still = img.makeImageAtCurrentFrame(); // $ExpectType Image | null
const ms = img.currentFrameDuration(); // $ExpectType number
img.reset();
const w = img.width(); // $ExpectType number
}
// In an effort to keep these type-checking tests easy to read and understand, we can "inject"
// types instead of actually having to create them from scratch. To inject them, we define them
// as an optional parameter and then have a null check to make sure that optional-ness does not
// cause errors.
function canvasTests(CK: CanvasKit, canvas?: Canvas, paint?: Paint, path?: Path,
img?: Image, aImg?: AnimatedImage, para?: Paragraph,
skp?: SkPicture, font?: Font, textBlob?: TextBlob, verts?: Vertices,
imageInfo?: ImageInfo, imgFilter?: ImageFilter) {
if (!canvas || !paint || !path || !img || !aImg || !para || !skp || !font ||
!textBlob || !verts || !imageInfo || !imgFilter) {
return;
}
const someColor = [0.9, 0.8, 0.7, 0.6]; // Making sure arrays are accepted as colors.
const someRect = [4, 3, 2, 1]; // Making sure arrays are accepted as rects.
// Making sure arrays are accepted as rrects.
const someRRect = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
const someMatrix = CK.Malloc(Float32Array, 16); // Make sure matrixes can be malloc'd.
canvas.clear(CK.RED);
canvas.clipPath(path, CK.ClipOp.Intersect, false);
canvas.clipRect(someRect, CK.ClipOp.Intersect, true);
canvas.clipRRect(CK.RRectXY(someRect, 10, 20), CK.ClipOp.Difference, true);
canvas.concat([1, 0, 0, 0, 1, 0, 0, 0, 1]);
canvas.concat(someMatrix);
canvas.drawArc(someRect, 0, 90, true, paint);
canvas.drawAtlas(img, [1, 2, 3, 4, 5, 6, 7, 8], [8, 7, 6, 5, 4, 3, 2, 1], paint);
canvas.drawAtlas(img, [1, 2, 3, 4, 5, 6, 7, 8], [8, 7, 6, 5, 4, 3, 2, 1], paint,
CK.BlendMode.Darken,
[CK.ColorAsInt(100, 110, 120), CK.ColorAsInt(130, 140, 150)]);
canvas.drawAtlas(img, [1, 2, 3, 4, 5, 6, 7, 8], [8, 7, 6, 5, 4, 3, 2, 1], paint,
null, null, {B: 0, C: 0.5});
canvas.drawAtlas(img, [1, 2, 3, 4, 5, 6, 7, 8], [8, 7, 6, 5, 4, 3, 2, 1], paint,
null, null, {filter: CK.FilterMode.Linear, mipmap: CK.MipmapMode.Nearest});
canvas.drawCircle(20, 20, 20, paint);
canvas.drawColor(someColor);
canvas.drawColor(someColor, CK.BlendMode.ColorDodge);
canvas.drawColorComponents(0.2, 1.0, -0.02, 0.5);
canvas.drawColorComponents(0.2, 1.0, -0.02, 0.5, CK.BlendMode.ColorDodge);
canvas.drawColorInt(CK.ColorAsInt(100, 110, 120));
canvas.drawColorInt(CK.ColorAsInt(100, 110, 120), CK.BlendMode.ColorDodge);
canvas.drawDRRect(someRRect, CK.RRectXY(someRect, 10, 20), paint);
canvas.drawImage(img, 0, -43);
canvas.drawImage(img, 0, -43, paint);
canvas.drawImageCubic(img, 0, -43, 1 / 3, 1 / 4, null);
canvas.drawImageOptions(img, 0, -43, CK.FilterMode.Nearest, CK.MipmapMode.Nearest, paint);
canvas.drawImageNine(img, someRect, someRect, CK.FilterMode.Nearest);
canvas.drawImageNine(img, CK.XYWHiRect(10, 20, 40, 40), someRect, CK.FilterMode.Linear, paint);
canvas.drawImageRect(img, someRect, someRect, paint);
canvas.drawImageRect(img, CK.XYWHRect(90, 90, 40, 40), someRect, paint);
canvas.drawImageRect(img, someRect, someRect, paint, true);
canvas.drawImageRectCubic(img, someRect, someRect, 1 / 5, 1 / 6);
canvas.drawImageRectCubic(img, someRect, someRect, 1 / 5, 1 / 6, paint);
canvas.drawImageRectOptions(img, someRect, someRect, CK.FilterMode.Linear, CK.MipmapMode.None);
canvas.drawImageRectOptions(img, someRect, someRect, CK.FilterMode.Linear, CK.MipmapMode.None, paint);
canvas.drawLine(1, 2, 3, 4, paint);
canvas.drawOval(someRect, paint);
canvas.drawPaint(paint);
canvas.drawParagraph(para, 10, 7);
const cubics = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12];
const colors = [CK.RED, CK.BLUE, CK.GREEN, CK.WHITE];
const texs = [1, 1, 2, 2, 3, 3, 4, 4];
canvas.drawPatch(cubics, null, null, null, paint);
canvas.drawPatch(cubics, colors, null, CK.BlendMode.Clear, paint);
canvas.drawPatch(cubics, null, texs, null, paint);
canvas.drawPatch(cubics, colors, texs, CK.BlendMode.SrcOver, paint);
canvas.drawPath(path, paint);
canvas.drawPicture(skp);
canvas.drawPoints(CK.PointMode.Lines, [1, 2, 3, 4, 5, 6], paint);
canvas.drawRect(someRect, paint);
canvas.drawRect4f(5, 6, 7, 8, paint);
canvas.drawRRect(someRRect, paint);
canvas.drawShadow(path, [1, 2, 3], [4, 5, 6], 7, someColor, CK.BLUE, 0);
const mallocedVector3 = CK.Malloc(Float32Array, 3);
canvas.drawShadow(path, mallocedVector3, mallocedVector3, 7, someColor, CK.BLUE, 0);
canvas.drawText('foo', 1, 2, paint, font);
canvas.drawTextBlob(textBlob, 10, 20, paint);
canvas.drawVertices(verts, CK.BlendMode.DstOut, paint);
const irect = canvas.getDeviceClipBounds(); // $ExpectType Int32Array
const irect2 = canvas.getDeviceClipBounds(irect); // $ExpectType Int32Array
const isCulled = canvas.quickReject(someRect); // $ExpectType boolean
const matrTwo = canvas.getLocalToDevice(); // $ExpectType Float32Array
const sc = canvas.getSaveCount(); // $ExpectType number
const matrThree = canvas.getTotalMatrix(); // $ExpectType number[]
const surface = canvas.makeSurface(imageInfo); // $ExpectType Surface | null
const pixels = canvas.readPixels(85, 1000, {// $Uint8Array | Float32Array | null
width: 79,
height: 205,
colorType: CK.ColorType.RGBA_8888,
alphaType: CK.AlphaType.Unpremul,
colorSpace: CK.ColorSpace.SRGB,
});
const m = CK.Malloc(Uint8Array, 10);
img.readPixels(85, 1000, {
width: 79,
height: 205,
colorType: CK.ColorType.RGBA_8888,
alphaType: CK.AlphaType.Unpremul,
colorSpace: CK.ColorSpace.SRGB,
}, m, 4 * 85);
canvas.restore();
canvas.restoreToCount(2);
canvas.rotate(1, 2, 3);
const height = canvas.save(); // $ExpectType number
const h2 = canvas.saveLayer(); // $ExpectType number
const h3 = canvas.saveLayer(paint); // $ExpectType number
const h4 = canvas.saveLayer(paint, someRect);
const h5 = canvas.saveLayer(paint, someRect, imgFilter, CK.SaveLayerF16ColorType);
const h6 = canvas.saveLayer(paint, someRect, null, CK.SaveLayerInitWithPrevious);
const h7 = canvas.saveLayer(paint, someRect, imgFilter, CK.SaveLayerInitWithPrevious, CK.TileMode.Decal);
canvas.scale(5, 10);
canvas.skew(10, 5);
canvas.translate(20, 30);
const ok = canvas.writePixels([1, 2, 3, 4], 1, 1, 10, 20); // $ExpectType boolean
const ok2 = canvas.writePixels([1, 2, 3, 4], 1, 1, 10, 20, CK.AlphaType.Premul,
CK.ColorType.Alpha_8, CK.ColorSpace.DISPLAY_P3);
}
function canvas2DTests(CK: CanvasKit) {
const bytes = new ArrayBuffer(10);
const canvas = CK.MakeCanvas(100, 200);
const img = canvas.decodeImage(bytes);
const ctx = canvas.getContext('2d');
ctx!.lineTo(2, 4);
canvas.loadFont(bytes, {
family: 'BungeeNonSystem',
style: 'normal',
weight: '400',
});
const p2d = canvas.makePath2D();
p2d.quadraticCurveTo(1, 2, 3, 4);
const iData = new CK.ImageData(40, 50);
const imgStr = canvas.toDataURL();
}
function colorTests(CK: CanvasKit) {
const colorOne = CK.Color(200, 200, 200, 0.8); // $ExpectType Float32Array
const colorTwo = CK.Color4f(0.8, 0.8, 0.8, 0.7); // $ExpectType Float32Array
const colorThree = CK.ColorAsInt(240, 230, 220); // $ExpectType number
const colorFour = CK.parseColorString('#887766'); // $ExpectType Float32Array
const colors = CK.computeTonalColors({ // $ExpectType TonalColorsOutput
ambient: colorOne,
spot: [0.2, 0.4, 0.6, 0.8],
});
// Deprecated Color functions
const [r, g, b, a] = CK.getColorComponents(colorTwo);
const alphaChanged = CK.multiplyByAlpha(colorOne, 0.1);
}
function colorFilterTests(CK: CanvasKit) {
const cf = CK.ColorFilter; // less typing
const filterOne = cf.MakeBlend(CK.CYAN, CK.BlendMode.ColorBurn); // $ExpectType ColorFilter
const filterTwo = cf.MakeLinearToSRGBGamma(); // $ExpectType ColorFilter
const filterThree = cf.MakeSRGBToLinearGamma(); // $ExpectType ColorFilter
const filterFour = cf.MakeCompose(filterOne, filterTwo); // $ExpectType ColorFilter
const filterFive = cf.MakeLerp(0.7, filterThree, filterFour); // $ExpectType ColorFilter
const filterSeven = cf.MakeBlend(CK.MAGENTA, CK.BlendMode.SrcOut, CK.ColorSpace.DISPLAY_P3); // $ExpectType ColorFilter
const r = CK.ColorMatrix.rotated(0, .707, -.707); // $ExpectType Float32Array
const b = CK.ColorMatrix.rotated(2, .5, .866);
const s = CK.ColorMatrix.scaled(0.9, 1.5, 0.8, 0.8);
let cm = CK.ColorMatrix.concat(r, s);
cm = CK.ColorMatrix.concat(cm, b);
CK.ColorMatrix.postTranslate(cm, 20, 0, -10, 0);
const filterSix = CK.ColorFilter.MakeMatrix(cm); // $ExpectType ColorFilter
const luma = CK.ColorFilter.MakeLuma(); // $ExpectType ColorFilter
}
function contourMeasureTests(CK: CanvasKit, path?: Path) {
if (!path) return;
const iter = new CK.ContourMeasureIter(path, true, 2); // $ExpectType ContourMeasureIter
const contour = iter.next(); // $ExpectType ContourMeasure | null
if (!contour) return;
const pt = contour.getPosTan(2); // $ExpectType Float32Array
contour.getPosTan(2, pt);
const segment = contour.getSegment(0, 20, true); // $ExpectType Path
const closed = contour.isClosed(); // $ExpectType boolean
const length = contour.length(); // $ExpectType number
}
function imageTests(CK: CanvasKit, imgElement?: HTMLImageElement) {
if (!imgElement) return;
const buff = new ArrayBuffer(10);
const img = CK.MakeImageFromEncoded(buff); // $ExpectType Image | null
const img2 = CK.MakeImageFromCanvasImageSource(imgElement); // $ExpectType Image
const img3 = CK.MakeImage({ // $ExpectType Image | null
width: 1,
height: 1,
alphaType: CK.AlphaType.Premul,
colorType: CK.ColorType.RGBA_8888,
colorSpace: CK.ColorSpace.SRGB
}, Uint8Array.of(255, 0, 0, 250), 4);
const img4 = CK.MakeLazyImageFromTextureSource(imgElement); // $ExpectType Image
const img5 = CK.MakeLazyImageFromTextureSource(imgElement, {
width: 1,
height: 1,
alphaType: CK.AlphaType.Premul,
colorType: CK.ColorType.RGBA_8888,
});
const img6 = CK.MakeLazyImageFromTextureSource(imgElement, {
width: 1,
height: 1,
alphaType: CK.AlphaType.Premul,
colorType: CK.ColorType.RGBA_8888,
}, true);
if (!img) return;
const dOne = img.encodeToBytes(); // $ExpectType Uint8Array | null
const dTwo = img.encodeToBytes(CK.ImageFormat.JPEG, 97);
const h = img.height();
const w = img.width();
const s1 = img.makeShaderCubic(CK.TileMode.Decal, CK.TileMode.Repeat, 1 / 3, 1 / 3); // $ExpectType Shader
const mm = img.makeCopyWithDefaultMipmaps(); // $ExpectType Image
const s2 = mm.makeShaderOptions(CK.TileMode.Decal, CK.TileMode.Repeat, // $ExpectType Shader
CK.FilterMode.Nearest, CK.MipmapMode.Linear,
CK.Matrix.identity());
// See https://github.com/microsoft/dtslint/issues/191#issuecomment-1108307671 for below
const pixels = img.readPixels(85, 1000, { // $ExpectType Float32Array | Uint8Array | null || Uint8Array | Float32Array | null
width: 79,
height: 205,
colorType: CK.ColorType.RGBA_8888,
alphaType: CK.AlphaType.Unpremul,
colorSpace: CK.ColorSpace.SRGB,
});
const m = CK.Malloc(Uint8Array, 10);
img.readPixels(85, 1000, {
width: 79,
height: 205,
colorType: CK.ColorType.RGBA_8888,
alphaType: CK.AlphaType.Unpremul,
colorSpace: CK.ColorSpace.SRGB,
}, m, 4 * 85);
const ii = img.getImageInfo(); // $ExpectType PartialImageInfo
const cs = img.getColorSpace(); // $ExpectType ColorSpace
cs.delete();
img.delete();
}
function imageFilterTests(CK: CanvasKit, colorFilter?: ColorFilter, img?: Image, shader?: Shader) {
if (!colorFilter || !img || !shader) return;
const imgf = CK.ImageFilter; // less typing
const filter = imgf.MakeBlur(2, 4, CK.TileMode.Mirror, null); // $ExpectType ImageFilter
const filter1 = imgf.MakeBlur(2, 4, CK.TileMode.Decal, filter); // $ExpectType ImageFilter
const filter2 = imgf.MakeColorFilter(colorFilter, null); // $ExpectType ImageFilter
const filter3 = imgf.MakeColorFilter(colorFilter, filter); // $ExpectType ImageFilter
const filter4 = imgf.MakeCompose(null, filter2); // $ExpectType ImageFilter
const filter5 = imgf.MakeCompose(filter3, null); // $ExpectType ImageFilter
const filter6 = imgf.MakeCompose(filter4, filter2); // $ExpectType ImageFilter
const filter7 = imgf.MakeMatrixTransform(CK.Matrix.scaled(2, 3, 10, 10),
{ B: 0, C: 0.5 }, null);
const filter8 = imgf.MakeMatrixTransform(CK.M44.identity(),
{ filter: CK.FilterMode.Linear, mipmap: CK.MipmapMode.Nearest },
filter6);
const filter9 = imgf.MakeMatrixTransform(CK.M44.identity(),
{ filter: CK.FilterMode.Nearest },
filter6);
let filter10 = imgf.MakeBlend(CK.BlendMode.SrcOver, filter8, filter9); // $ExpectType ImageFilter
filter10 = imgf.MakeBlend(CK.BlendMode.Xor, null, null);
let filter11 = imgf.MakeDilate(2, 10, null); // $ExpectType ImageFilter
filter11 = imgf.MakeDilate(2, 10, filter11);
let filter12 = imgf.MakeErode(2, 10, null); // $ExpectType ImageFilter
filter12 = imgf.MakeErode(2, 10, filter12);
let filter13 = imgf.MakeDisplacementMap(// $ExpectType ImageFilter
CK.ColorChannel.Red, CK.ColorChannel.Alpha, 3.2, filter11, filter12);
filter13 = imgf.MakeDisplacementMap(
CK.ColorChannel.Blue, CK.ColorChannel.Green, 512, null, null);
let filter14 = imgf.MakeDropShadow(10, -30, 4.0, 2.0, CK.MAGENTA, null); // $ExpectType ImageFilter
filter14 = imgf.MakeDropShadow(10, -30, 4.0, 2.0, CK.MAGENTA, filter14);
filter14 = imgf.MakeDropShadowOnly(10, -30, 4.0, 2.0, CK.CYAN, null);
filter14 = imgf.MakeDropShadowOnly(10, -30, 4.0, 2.0, CK.CYAN, filter14);
let filter15 = imgf.MakeImage(img, { B: 1 / 3, C: 1 / 3 }); // $ExpectType ImageFilter | null
filter15 = imgf.MakeImage(img, { filter: CK.FilterMode.Linear },
CK.LTRBRect(1, 2, 3, 4), CK.XYWHRect(5, 6, 7, 8));
let filter16 = imgf.MakeOffset(5, 3, null); // $ExpectType ImageFilter
filter16 = imgf.MakeOffset(-100.3, -18, filter16);
imgf.MakeShader(shader); // $ExpectType ImageFilter
}
function fontTests(CK: CanvasKit, face?: Typeface, paint?: Paint) {
if (!face || !paint) return;
const font = new CK.Font(); // $ExpectType Font
const f2 = new CK.Font(face); // $ExpectType Font
const f3 = new CK.Font(null); // $ExpectType Font
const f4 = new CK.Font(face, 20); // $ExpectType Font
const f5 = new CK.Font(null, 20); // $ExpectType Font
const f6 = new CK.Font(null, 20, 2, 3); // $ExpectType Font
const f7 = new CK.Font(face, 20, 4, 5); // $ExpectType Font
const glyphMalloc = CK.MallocGlyphIDs(20);
const someGlyphs = [1, 2, 3, 4, 5];
const glyphBounds = font.getGlyphBounds(glyphMalloc, paint); // $ExpectType Float32Array
font.getGlyphBounds(someGlyphs, null, glyphBounds);
const ids = font.getGlyphIDs('abcd');
font.getGlyphIDs('efgh', 4, ids);
const widths = font.getGlyphWidths(glyphMalloc, paint);
font.getGlyphWidths(someGlyphs, null, widths);
const sects = font.getGlyphIntercepts(ids, [10, 20], -60, -40);
font.getScaleX();
font.getSize();
font.getSkewX();
font.getTypeface();
font.setEdging(CK.FontEdging.Alias);
font.setEmbeddedBitmaps(true);
font.setHinting(CK.FontHinting.Slight);
font.setLinearMetrics(true);
font.setScaleX(5);
font.setSize(15);
font.setSkewX(2);
font.setSubpixel(true);
font.setTypeface(null);
font.setTypeface(face);
}
function fontMgrTests(CK: CanvasKit) {
const buff1 = new ArrayBuffer(10);
const buff2 = new ArrayBuffer(20);
const fm = CK.FontMgr.FromData(buff1, buff2)!;
fm.countFamilies();
fm.getFamilyName(0);
}
function globalTests(CK: CanvasKit, path?: Path) {
if (!path) {
return;
}
const n = CK.getDecodeCacheLimitBytes();
const u = CK.getDecodeCacheUsedBytes();
CK.setDecodeCacheLimitBytes(1000);
const matr = CK.Matrix.rotated(Math.PI / 6);
const p = CK.getShadowLocalBounds(matr, path, [0, 0, 1], [500, 500, 20], 20,
CK.ShadowDirectionalLight | CK.ShadowGeometricOnly | CK.ShadowDirectionalLight);
const mallocedVector3 = CK.Malloc(Float32Array, 3);
const q = CK.getShadowLocalBounds(matr, path, mallocedVector3, mallocedVector3, 20,
CK.ShadowDirectionalLight | CK.ShadowGeometricOnly | CK.ShadowDirectionalLight);
}
function paintTests(CK: CanvasKit, colorFilter?: ColorFilter, imageFilter?: ImageFilter,
maskFilter?: MaskFilter, pathEffect?: PathEffect, shader?: Shader) {
if (!colorFilter || !colorFilter || !imageFilter || !maskFilter || !pathEffect || !shader) {
return;
}
const paint = new CK.Paint(); // $ExpectType Paint
const newPaint = paint.copy(); // $ExpectType Paint
const color = paint.getColor(); // $ExpectType Float32Array
const sc = paint.getStrokeCap();
const sj = paint.getStrokeJoin();
const limit = paint.getStrokeMiter(); // $ExpectType number
const width = paint.getStrokeWidth(); // $ExpectType number
paint.setAlphaf(0.8);
paint.setAntiAlias(true);
paint.setBlendMode(CK.BlendMode.DstOut);
paint.setColor(CK.RED);
paint.setColor([0, 0, 1.2, 0.5], CK.ColorSpace.DISPLAY_P3);
paint.setColorComponents(0, 0, 0.9, 1.0);
paint.setColorComponents(0, 0, 1.2, 0.5, CK.ColorSpace.DISPLAY_P3);
paint.setColorFilter(colorFilter);
paint.setColorInt(CK.ColorAsInt(20, 30, 40));
paint.setColorInt(CK.ColorAsInt(20, 30, 40), CK.ColorSpace.SRGB);
paint.setDither(true);
paint.setImageFilter(imageFilter);
paint.setMaskFilter(maskFilter);
paint.setPathEffect(pathEffect);
// @ts-expect-error
paint.setShader(colorFilter);
paint.setShader(shader);
paint.setStrokeCap(CK.StrokeCap.Round);
paint.setStrokeJoin(CK.StrokeJoin.Miter);
paint.setStrokeMiter(10);
paint.setStrokeWidth(20);
paint.setStyle(CK.PaintStyle.Fill);
paint.delete();
}
function pathTests(CK: CanvasKit) {
const path = new CK.Path(); // $ExpectType Path
const builder = new CK.PathBuilder(); // $ExpectType PathBuilder
const builder2 = new CK.PathBuilder(path); // $ExpectType PathBuilder
const builder3 = new CK.PathBuilder(CK.FillType.EvenOdd); // $ExpectType PathBuilder
const p2 = CK.Path.MakeFromCmds([ // $ExpectType Path | null
CK.MOVE_VERB, 0, 10,
CK.LINE_VERB, 30, 40,
CK.QUAD_VERB, 20, 50, 45, 60,
]);
const verbs = CK.Malloc(Uint8Array, 10);
const points = CK.Malloc(Float32Array, 10);
const p3 = CK.Path.MakeFromVerbsPointsWeights(verbs, [1, 2, 3, 4]); // $ExpectType Path
const p4 = CK.Path.MakeFromVerbsPointsWeights([CK.CONIC_VERB], points, [2.3]);
const p5 = CK.Path.MakeFromOp(p4, p2!, CK.PathOp.ReverseDifference); // $ExpectType Path | null
const p6 = CK.Path.MakeFromSVGString('M 205,5 L 795,5 z'); // $ExpectType Path | null
const p7 = p3.makeAsWinding(); // $ExpectType Path | null
const p8 = builder.snapshot(); // $ExpectType Path
const someRect = CK.LTRBRect(10, 20, 30, 40);
// Making sure arrays are accepted as rrects.
const someRRect = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
builder.addArc(someRect, 0, 270);
builder.addOval(someRect);
builder.addOval(someRect, true, 3);
builder.addPath(p2);
builder.addPolygon([20, 20, 40, 40, 20, 40], true);
builder.addRect(someRect);
builder.addRect(someRect, true);
builder.addCircle(10, 10, 10);
builder.addRRect(someRRect);
builder.addRRect(someRRect, true);
builder.addVerbsPointsWeights(verbs, [1, 2, 3, 4]);
builder.addVerbsPointsWeights([CK.CONIC_VERB], points, [2.3]);
builder.arc(0, 0, 10, 0, Math.PI / 2);
builder.arc(0, 0, 10, 0, Math.PI / 2, true);
builder.arcToOval(someRect, 15, 60, true);
builder.arcToRotated(2, 4, 90, false, true, 0, 20);
builder.arcToTangent(20, 20, 40, 50, 2);
builder.close();
builder.detach();
builder.detachAndDelete();
let bounds = path.computeTightBounds(); // $ExpectType Float32Array
path.computeTightBounds(bounds);
builder.conicTo(1, 2, 3, 4, 5);
let ok = path.contains(10, 20); // $ExpectType boolean
const pCopy = path.copy(); // $ExpectType Path
const count = path.countPoints(); // $ExpectType number
builder.cubicTo(10, 10, 10, 10, 10, 10);
let r = path.makeDashed(8, 4, 1); // $ExpectType Path | null
ok = path.equals(pCopy);
bounds = path.getBounds(); // $ExpectType Float32Array
path.getBounds(bounds);
const ft = path.getFillType();
const pt = path.getPoint(7); // $ExpectType Float32Array
path.getPoint(8, pt);
ok = path.isEmpty();
builder.lineTo(10, -20);
builder.moveTo(-20, -30);
builder.offset(100, 100);
r = path.makeCombined(p2!, CK.PathOp.Difference);
builder.quadTo(10, 20, 30, 40);
builder.rArcTo(10, 10, 90, false, true, 2, 4);
builder.rConicTo(-1, 2, 4, 9, 3);
builder.rCubicTo(20, 30, 40, 50, 2, 1);
builder.rLineTo(20, 30);
builder.rMoveTo(40, 80);
builder.rQuadTo(1, 2, 3, 4);
path.setFillType(CK.FillType.EvenOdd);
builder.setFillType(CK.FillType.EvenOdd);
r = path.makeSimplified();
r = path.makeStroked();
r = path.makeStroked({});
r = path.makeStroked({
width: 20,
miter_limit: 9,
precision: 0.5,
cap: CK.StrokeCap.Butt,
join: CK.StrokeJoin.Miter,
});
const cmds = path.toCmds(); // $ExpectType Float32Array
const str = path.toSVGString(); // $ExpectType string
builder.transform(CK.Matrix.identity());
builder.transform(1, 0, 0, 0, 1, 0, 0, 0, 1);
path.makeTrimmed(0.1, 0.7, false);
if (CK.Path.CanInterpolate(p3, p4)) {
const interpolated = CK.Path.MakeFromPathInterpolation(p3, p4, 0.5); // $ExpectType Path | null
}
}
function paragraphTests(CK: CanvasKit, p?: Paragraph) {
if (!p) return;
const a = p.didExceedMaxLines(); // $ExpectType boolean
const b = p.getAlphabeticBaseline(); // $ExpectType number
const c = p.getGlyphPositionAtCoordinate(10, 3); // $ExpectType PositionWithAffinity
const d = p.getHeight(); // $ExpectType number
const e = p.getIdeographicBaseline(); // $ExpectType number
const f = p.getLongestLine(); // $ExpectType number
const g = p.getMaxIntrinsicWidth(); // $ExpectType number
const h = p.getMaxWidth(); // $ExpectType number
const i = p.getMinIntrinsicWidth(); // $ExpectType number
const j = p.getRectsForPlaceholders(); // $ExpectType RectWithDirection[]
const k = p.getRectsForRange(2, 10, CK.RectHeightStyle.Max, // $ExpectType RectWithDirection[]
CK.RectWidthStyle.Tight);
j[0].rect.length === 4;
j[0].dir === CK.TextDirection.RTL;
const l = p.getWordBoundary(10); // $ExpectType URange
p.layout(300);
const m = p.getLineMetrics(); // $ExpectType LineMetrics[]
const n = CK.GlyphRunFlags.IsWhiteSpace === 1;
const o = p.unresolvedCodepoints(); // $ExpectType number[]
const q = p.getLineMetricsAt(0); // $ExpectType LineMetrics | null
const r = p.getNumberOfLines(); // $ExpectType number
const s = p.getLineNumberAt(0); // $ExpectType number
const t = p.getGlyphInfoAt(0); // $ExpectType GlyphInfo | null
const u = p.getClosestGlyphInfoAtCoordinate(10, 3); // $ExpectType GlyphInfo | null
}
function paragraphBuilderTests(CK: CanvasKit, fontMgr?: FontMgr, paint?: Paint) {
if (!fontMgr || !paint) return;
const paraStyle = new CK.ParagraphStyle({ // $ExpectType ParagraphStyle
textStyle: {
color: CK.BLACK,
fontFamilies: ['Noto Serif'],
fontSize: 20,
},
textAlign: CK.TextAlign.Center,
maxLines: 8,
ellipsis: '.._.',
strutStyle: {
strutEnabled: true,
fontFamilies: ['Roboto'],
fontSize: 28,
heightMultiplier: 1.5,
forceStrutHeight: true,
},
disableHinting: true,
heightMultiplier: 2.5,
textDirection: CK.TextDirection.LTR,
textHeightBehavior: CK.TextHeightBehavior.DisableFirstAscent
});
const blueText = new CK.TextStyle({ // $ExpectType TextStyle
backgroundColor: CK.Color(234, 208, 232), // light pink
color: CK.Color(48, 37, 199),
fontFamilies: ['Noto Serif'],
decoration: CK.LineThroughDecoration,
decorationStyle: CK.DecorationStyle.Dashed,
decorationThickness: 1.5, // multiplier based on font size
fontSize: 24,
fontFeatures: [{name: 'smcp', value: 1}],
fontVariations: [{axis: 'wght', value: 100}],
shadows: [{color: CK.BLACK, blurRadius: 15},
{color: CK.RED, blurRadius: 5, offset: [10, 10]}],
});
const builder = CK.ParagraphBuilder.Make(paraStyle, fontMgr); // $ExpectType ParagraphBuilder
builder.pushStyle(blueText);
builder.addText('VAVAVAVAVAVAVA\nVAVA\n');
builder.pop();
const paragraph = builder.build(); // $ExpectType Paragraph
const buf = new ArrayBuffer(10);
const fontSrc = CK.TypefaceFontProvider.Make(); // $ExpectType TypefaceFontProvider
fontSrc.registerFont(buf, 'sans-serif');
const builder2 = CK.ParagraphBuilder.MakeFromFontProvider(// $ExpectType ParagraphBuilder
paraStyle, fontSrc);
builder2.pushPaintStyle(blueText, paint, paint);
builder2.addPlaceholder();
builder2.addPlaceholder(10, 20, CK.PlaceholderAlignment.Top, CK.TextBaseline.Ideographic, 3);
builder2.reset();
const text = builder.getText(); // $ExpectType string
builder.setWordsUtf16(new Uint32Array(10));
builder.setGraphemeBreaksUtf16(new Uint32Array(10));
builder.setLineBreaksUtf16(new Uint32Array(10));
const paragraph3 = builder.build(); // $ExpectType Paragraph
const fontCollection = CK.FontCollection.Make(); // $ExpectType FontCollection
fontCollection.enableFontFallback();
fontCollection.setDefaultFontManager(fontSrc);
const fcBuilder = CK.ParagraphBuilder.MakeFromFontCollection(// $ExpectType ParagraphBuilder
paraStyle, fontCollection);
fcBuilder.addText('12345');
const fcParagraph = fcBuilder.build();
}
function pathEffectTests(CK: CanvasKit, path?: Path) {
if (!path) {
return;
}
const pe1 = CK.PathEffect.MakeCorner(2); // $ExpectType PathEffect | null
const pe2 = CK.PathEffect.MakeDash([2, 4]); // $ExpectType PathEffect
const pe3 = CK.PathEffect.MakeDash([2, 4, 6, 8], 10); // $ExpectType PathEffect
const pe4 = CK.PathEffect.MakeDiscrete(10, 2, 0); // $ExpectType PathEffect
const pe5 = CK.PathEffect.MakePath1D(path, 3, 4, CK.Path1DEffect.Morph); // $ExpectType PathEffect | null
const matr = CK.Matrix.scaled(3, 2);
const pe6 = CK.PathEffect.MakePath2D(matr, path); // $ExpectType PathEffect | null
const pe7 = CK.PathEffect.MakeLine2D(3.2, matr); // $ExpectType PathEffect | null
}
function mallocTests(CK: CanvasKit) {
const mFoo = CK.Malloc(Float32Array, 5);
const mArray = mFoo.toTypedArray(); // $ExpectType TypedArray
mArray[3] = 1.7;
const mSubArray = mFoo.subarray(0, 2); // $ExpectType TypedArray
mSubArray[0] = 2;
CK.Free(mFoo);
}
function maskFilterTests(CK: CanvasKit) {
const mf = CK.MaskFilter.MakeBlur(CK.BlurStyle.Solid, 8, false); // $ExpectType MaskFilter
}
function matrixTests(CK: CanvasKit) {
const m33 = CK.Matrix; // less typing
const matrA = m33.identity(); // $ExpectType number[]
const matrB = m33.rotated(0.1); // $ExpectType number[]
const matrC = m33.rotated(0.1, 15, 20); // $ExpectType number[]
const matrD = m33.multiply(matrA, matrB); // $ExpectType number[]
const matrE = m33.multiply(matrA, matrB, matrC, matrB, matrA); // $ExpectType number[]
const matrF = m33.scaled(1, 2); // $ExpectType number[]
const matrG = m33.scaled(1, 2, 3, 4); // $ExpectType number[]
const matrH = m33.skewed(1, 2); // $ExpectType number[]
const matrI = m33.skewed(1, 2, 3, 4); // $ExpectType number[]
const matrJ = m33.translated(1, 2); // $ExpectType number[]
const matrK = m33.invert(matrJ);
const m44 = CK.M44;
const matr1 = m44.identity(); // $ExpectType number[]
const matr2 = m44.invert(matr1);
const matr3 = m44.lookat([1, 2, 3], [4, 5, 6], [7, 8, 9]); // $ExpectType number[]
const matr4 = m44.multiply(matr1, matr3); // $ExpectType number[]
const matr5 = m44.mustInvert(matr1); // $ExpectType number[]
const matr6 = m44.perspective(1, 8, 0.4); // $ExpectType number[]
const matr7 = m44.rc(matr6, 0, 3); // $ExpectType number
const matr8 = m44.rotated([2, 3, 4], -0.4); // $ExpectType number[]
const matr9 = m44.rotatedUnitSinCos([4, 3, 2], 0.9, 0.1); // $ExpectType number[]
const matr10 = m44.scaled([5, 5, 5]); // $ExpectType number[]
const matr11 = m44.setupCamera(CK.LTRBRect(1, 2, 3, 4), 0.4, {
eye: [0, 0, 1],
coa: [0, 0, 0],
up: [0, 1, 0],
near: 0.2,
far: 4,
angle: Math.PI / 12,
});
const matr12 = m44.translated([3, 2, 1]); // $ExpectType number[]
const matr13 = m44.transpose([4, 5, 8]); // $ExpectType number[]
}
function pictureTests(CK: CanvasKit) {
const recorder = new CK.PictureRecorder(); // $ExpectType PictureRecorder
const canvas = recorder.beginRecording(CK.LTRBRect(0, 0, 100, 100)); // $ExpectType Canvas
const pic = recorder.finishRecordingAsPicture(); // $ExpectType SkPicture
const bytes = pic.serialize(); // $ExpectType Uint8Array | null
const cullRect = pic.cullRect(); // $ExpectType Float32Array
const approxBytesUsed = pic.approximateBytesUsed(); // $ExpectType number
const pic2 = CK.MakePicture(bytes!);
const shader1 = pic2!.makeShader(CK.TileMode.Clamp, CK.TileMode.Decal, CK.FilterMode.Nearest);
const shader2 = pic2!.makeShader(CK.TileMode.Clamp, CK.TileMode.Decal, CK.FilterMode.Nearest,
CK.Matrix.rotated(3));
const shader3 = pic2!.makeShader(CK.TileMode.Clamp, CK.TileMode.Decal, CK.FilterMode.Nearest,
CK.Matrix.skewed(2, 1), CK.LTRBRect(3, 4, 5, 6));
}
function rectangleTests(CK: CanvasKit) {
const rectOne = CK.LTRBRect(5, 10, 20, 30); // $ExpectType Float32Array
const rectTwo = CK.XYWHRect(5, 10, 15, 20); // $ExpectType Float32Array
const iRectOne = CK.LTRBiRect(105, 110, 120, 130); // $ExpectType Int32Array
const iRectTwo = CK.XYWHiRect(105, 110, 15, 20); // $ExpectType Int32Array
const rrectOne = CK.RRectXY(rectOne, 3, 7); // $ExpectType Float32Array
}
function runtimeEffectTests(CK: CanvasKit) {
const rt = CK.RuntimeEffect.Make('not real sksl code'); // $ExpectType RuntimeEffect | null
if (!rt) return;
const rt2 = CK.RuntimeEffect.Make('not real sksl code', (err) => {
console.log(err);
});
const someMatr = CK.Matrix.translated(2, 60);
const s1 = rt.makeShader([0, 1]); // $ExpectType Shader
const s2 = rt.makeShader([0, 1], someMatr); // $ExpectType Shader
const s3 = rt.makeShaderWithChildren([4, 5], [s1, s2]); // $ExpectType Shader
const s4 = rt.makeShaderWithChildren([4, 5], [s1, s2], someMatr); // $ExpectType Shader
const a = rt.getUniform(1); // $ExpectType SkSLUniform
const b = rt.getUniformCount(); // $ExpectType number
const c = rt.getUniformFloatCount(); // $ExpectType number
const d = rt.getUniformName(3); // $ExpectType string
}
function skottieTests(CK: CanvasKit, canvas?: Canvas) {
if (!canvas) return;
const anim = CK.MakeAnimation('some json'); // $ExpectType SkottieAnimation
const a = anim.duration(); // $ExpectType number
const b = anim.fps(); // $ExpectType number
const c = anim.version(); // $ExpectType string
const d = anim.size(); // $ExpectType Float32Array
anim.size(d);
const rect = anim.seek(0.5);
anim.seek(0.6, rect);
const rect2 = anim.seekFrame(12.3);
anim.seekFrame(12.3, rect2);
anim.render(canvas);
anim.render(canvas, rect);
const buff = new ArrayBuffer(10);
const mAnim = CK.MakeManagedAnimation('other json', { // $ExpectType ManagedSkottieAnimation
'flightAnim.gif': buff,
});
const textProp = new CK.SlottableTextProperty({ // $ExpectType SlottableTextProperty
fillColor: CK.Color(48, 37, 199),
strokeColor: CK.Color(0, 100, 100)
});
mAnim.setColor('slider', CK.WHITE);
mAnim.setOpacity('slider', 0.8);
const e = mAnim.getMarkers(); // $ExpectType AnimationMarker[]
const f = mAnim.getColorProps(); // $ExpectType ColorProperty[]
const g = mAnim.getOpacityProps(); // $ExpectType OpacityProperty[]
const h = mAnim.getTextProps(); // $ExpectType TextProperty[]
const i = mAnim.setColor('foo', CK.RED); // $ExpectType boolean
const j = mAnim.setOpacity('foo', 0.5); // $ExpectType boolean
const k = mAnim.setText('foo', 'bar', 12); // $ExpectType boolean
const l = mAnim.setTransform('foo', [1, 2], [3, 4], [5, 6], 90, 1, 0); // $ExpectType boolean
const m = mAnim.setColorSlot('foo', CK.BLUE); // $ExpectType boolean
const n = mAnim.setScalarSlot('foo', 5); // $ExpectType boolean
const o = mAnim.setVec2Slot('foo', [1, 2]); // $ExpectType boolean
const p = mAnim.setImageSlot('foo', 'bar'); // $ExpectType boolean
const q = mAnim.setTextSlot('foo', textProp); // $ExpectType boolean
const r = mAnim.getColorSlot('foo'); // $ExpectType Float32Array | null
const s = mAnim.getScalarSlot('foo'); // $ExpectType number | null
const t = mAnim.getVec2Slot('foo'); // $ExpectType Float32Array | null
const u = mAnim.getTextSlot('foo'); // $ExpectType SlottableTextProperty | null
const v = mAnim.getSlotInfo(); // $ExpectType SlotInfo
}
function shaderTests(CK: CanvasKit) {
const s1 = CK.Shader.MakeColor([0.8, 0.2, 0.5, 0.9], // $ExpectType Shader
CK.ColorSpace.SRGB);
const s2 = CK.Shader.MakeBlend(CK.BlendMode.Src, s1, s1); // $ExpectType Shader
const s4 = CK.Shader.MakeLinearGradient(// $ExpectType Shader
[0, 0], [50, 100],
Float32Array.of(
0, 1, 0, 0.8,
1, 0, 0, 1,
0, 0, 1, 0.5,
),
[0, 0.65, 1.0],
CK.TileMode.Mirror
);
const s5 = CK.Shader.MakeLinearGradient(// $ExpectType Shader
[0, 0], [50, 100],
Float32Array.of(
0, 1, 0, 0.8,
1, 0, 0, 1,
0, 0, 1, 0.5,
),
null,
CK.TileMode.Clamp,
CK.Matrix.rotated(Math.PI / 4, 0, 100),
1,
CK.ColorSpace.SRGB,
);
const s6 = CK.Shader.MakeRadialGradient(// $ExpectType Shader
[0, 0], 50,
Float32Array.of(
0, 1, 0, 0.8,
1, 0, 0, 1,
0, 0, 1, 0.5,
),
[0, 0.65, 1.0],
CK.TileMode.Decal,
);
const s7 = CK.Shader.MakeRadialGradient(// $ExpectType Shader
[0, 0], 50,
Float32Array.of(
0, 1, 0, 0.8,
1, 0, 0, 1,
0, 0, 1, 0.5,
),
null,
CK.TileMode.Clamp,
CK.Matrix.skewed(3, -3),
1,
CK.ColorSpace.SRGB,
);
const s8 = CK.Shader.MakeTwoPointConicalGradient(// $ExpectType Shader
[0, 0], 20,
[50, 100], 60,
Float32Array.of(
0, 1, 0, 0.8,
1, 0, 0, 1,
0, 0, 1, 0.5,
),
[0, 0.65, 1.0],
CK.TileMode.Mirror
);
const s9 = CK.Shader.MakeTwoPointConicalGradient(// $ExpectType Shader
[0, 0], 20,
[50, 100], 60,
Float32Array.of(
0, 1, 0, 0.8,
1, 0, 0, 1,
0, 0, 1, 0.5,
),
[0, 0.65, 1.0],
CK.TileMode.Mirror,
CK.Matrix.rotated(Math.PI / 4, 0, 100),
1,
CK.ColorSpace.SRGB,
);
const s10 = CK.Shader.MakeSweepGradient(// $ExpectType Shader
0, 20,
Float32Array.of(
0, 1, 0, 0.8,
1, 0, 0, 1,
0, 0, 1, 0.5,
),
[0, 0.65, 1.0],
CK.TileMode.Mirror
);
const s11 = CK.Shader.MakeSweepGradient(// $ExpectType Shader
0, 20,
Float32Array.of(
0, 1, 0, 0.8,
1, 0, 0, 1,
0, 0, 1, 0.5,
),
null,
CK.TileMode.Mirror,
CK.Matrix.rotated(Math.PI / 4, 0, 100),
1,
15, 275, // start, end angle in degrees.
CK.ColorSpace.SRGB,
);
const s12 = CK.Shader.MakeFractalNoise(0.1, 0.05, 2, 0, 80, 80); // $ExpectType Shader
const s13 = CK.Shader.MakeTurbulence(0.1, 0.05, 2, 0, 80, 80); // $ExpectType Shader
}
function surfaceTests(CK: CanvasKit, gl?: WebGLRenderingContext) {
if (!gl) {
return;
}
const canvasEl = document.querySelector('canvas') as HTMLCanvasElement;
const surfaceOne = CK.MakeCanvasSurface(canvasEl)!; // $ExpectType Surface
const surfaceTwo = CK.MakeCanvasSurface('my_canvas')!;
const surfaceThree = CK.MakeSWCanvasSurface(canvasEl)!; // $ExpectType Surface
const surfaceFour = CK.MakeSWCanvasSurface('my_canvas')!;
const surfaceFive = CK.MakeWebGLCanvasSurface(canvasEl, // $ExpectType Surface
CK.ColorSpace.SRGB, {
majorVersion: 2,
preferLowPowerToHighPerformance: 1,
})!;
const surfaceSix = CK.MakeWebGLCanvasSurface('my_canvas', CK.ColorSpace.DISPLAY_P3, {
enableExtensionsByDefault: 2,
})!;
const surfaceSeven = CK.MakeSurface(200, 200)!; // $ExpectType Surface
const m = CK.Malloc(Uint8Array, 5 * 5 * 4);
const surfaceEight = CK.MakeRasterDirectSurface({
width: 5,
height: 5,
colorType: CK.ColorType.RGBA_8888,
alphaType: CK.AlphaType.Premul,
colorSpace: CK.ColorSpace.SRGB,
}, m, 20);
surfaceOne.flush();
const canvas = surfaceTwo.getCanvas(); // $ExpectType Canvas
const ii = surfaceThree.imageInfo(); // $ExpectType ImageInfo
const h = surfaceFour.height(); // $ExpectType number
const w = surfaceFive.width(); // $ExpectType number
const subsurface = surfaceOne.makeSurface(ii); // $ExpectType Surface
const isGPU = subsurface.reportBackendTypeIsGPU(); // $ExpectType boolean
const count = surfaceThree.sampleCnt(); // $ExpectType number
const img = surfaceFour.makeImageSnapshot([0, 3, 2, 5]); // $ExpectType Image
const img2 = surfaceSix.makeImageSnapshot(); // $ExpectType Image
const img3 = surfaceFour.makeImageFromTexture(gl.createTexture()!, {
height: 40,
width: 80,
colorType: CK.ColorType.RGBA_8888,
alphaType: CK.AlphaType.Unpremul,
colorSpace: CK.ColorSpace.SRGB,
});
const img4 = surfaceFour.makeImageFromTextureSource(new Image()); // $ExpectType Image | null
const videoEle = document.createElement('video');
const img5 = surfaceFour.makeImageFromTextureSource(videoEle, {
height: 40,
width: 80,
colorType: CK.ColorType.RGBA_8888,
alphaType: CK.AlphaType.Unpremul,
});
const img6 = surfaceFour.makeImageFromTextureSource(new ImageData(40, 80)); // $ExpectType Image | null
const img7 = surfaceFour.makeImageFromTextureSource(videoEle, {
height: 40,
width: 80,
colorType: CK.ColorType.RGBA_8888,
alphaType: CK.AlphaType.Premul,
}, true);
surfaceSeven.delete();
const ctx = CK.GetWebGLContext(canvasEl); // $ExpectType number
CK.deleteContext(ctx);
const grCtx = CK.MakeGrContext(ctx);
const surfaceNine = CK.MakeOnScreenGLSurface(grCtx!, 100, 400, // $ExpectType Surface
CK.ColorSpace.ADOBE_RGB)!;
const sample = gl.getParameter(gl.SAMPLES);
const stencil = gl.getParameter(gl.STENCIL_BITS);
const surfaceTen = CK.MakeOnScreenGLSurface(grCtx!, 100, 400, // $ExpectType Surface
CK.ColorSpace.ADOBE_RGB, sample, stencil)!;
const rt = CK.MakeRenderTarget(grCtx!, 100, 200); // $ExpectType Surface | null
const rt2 = CK.MakeRenderTarget(grCtx!, { // $ExpectType Surface | null
width: 79,
height: 205,
colorType: CK.ColorType.RGBA_8888,
alphaType: CK.AlphaType.Premul,
colorSpace: CK.ColorSpace.SRGB,
});
const drawFrame = (canvas: Canvas) => {
canvas.clear([0, 0, 0, 0]);
};
surfaceFour.requestAnimationFrame(drawFrame);
surfaceFour.drawOnce(drawFrame);
surfaceFour.updateTextureFromSource(img5!, videoEle);
surfaceFour.updateTextureFromSource(img5!, videoEle, true);
}
function textBlobTests(CK: CanvasKit, font?: Font, path?: Path) {
if (!font || !path) return;
const tb = CK.TextBlob; // less typing
const ids = font.getGlyphIDs('abc');
const mXforms = CK.Malloc(Float32Array, ids.length * 4);
const blob = tb.MakeFromGlyphs([5, 6, 7, 8], font); // $ExpectType TextBlob
const blob1 = tb.MakeFromGlyphs(ids, font); // $ExpectType TextBlob
const blob2 = tb.MakeFromRSXform('cdf', mXforms, font); // $ExpectType TextBlob
const blob3 = tb.MakeFromRSXform('c', [-1, 0, 2, 3], font); // $ExpectType TextBlob
const blob4 = tb.MakeFromRSXformGlyphs([3, 6], mXforms, font); // $ExpectType TextBlob
const blob5 = tb.MakeFromRSXformGlyphs(ids, [-1, 0, 2, 3], font); // $ExpectType TextBlob
const blob6 = tb.MakeFromText('xyz', font); // $ExpectType TextBlob
const blob7 = tb.MakeOnPath('tuv', path, font); // $ExpectType TextBlob
const blob8 = tb.MakeOnPath('tuv', path, font, 10); // $ExpectType TextBlob
}
function typefaceTests(CK: CanvasKit) {
const face = CK.Typeface.MakeTypefaceFromData(new ArrayBuffer(10));
const face2 = CK.Typeface.GetDefault(); // $ExpectType Typeface | null
const ids = face!.getGlyphIDs('abcd');
face!.getGlyphIDs('efgh', 4, ids);
}
function vectorTests(CK: CanvasKit) {
const a = [1, 2, 3];
const b = [4, 5, 6];
const vec = CK.Vector; // less typing
const v1 = vec.add(a, b); // $ExpectType VectorN
const v2 = vec.cross(a, b); // $ExpectType Vector3
const n1 = vec.dist(a, b); // $ExpectType number
const n2 = vec.dot(a, b); // $ExpectType number
const n3 = vec.length(a); // $ExpectType number
const n4 = vec.lengthSquared(a); // $ExpectType number
const v3 = vec.mulScalar(a, 10); // $ExpectType VectorN
const v4 = vec.normalize(a); // $ExpectType VectorN
const v5 = vec.sub(a, b); // $ExpectType VectorN
}
function verticesTests(CK: CanvasKit) {
const points = [
70, 170, 40, 90, 130, 150, 100, 50,
225, 150, 225, 60, 310, 180, 330, 100,
];
const textureCoordinates = [
0, 240, 0, 0, 80, 240, 80, 0,
160, 240, 160, 0, 240, 240, 240, 0,
];
const vertices = CK.MakeVertices(CK.VertexMode.TrianglesStrip, // $ExpectType Vertices
points, textureCoordinates);
const points2 = new Float32Array(points);
// 1d float color array
const colors = Float32Array.of(
1, 0, 0, 1, // red
0, 1, 0, 1, // green
0, 0, 1, 1, // blue
1, 0, 1, 1); // purple
const vertices2 = CK.MakeVertices(CK.VertexMode.TriangleFan,
points2, null, colors, null, true);
const rect = vertices.bounds(); // $ExpectType Float32Array
vertices.bounds(rect);
const id = vertices.uniqueID(); // $ExpectType number
}
function webGPUTest(CK: CanvasKit, device?: GPUDevice, canvas?: HTMLCanvasElement, texture?: GPUTexture) {
if (!device || !canvas || !texture) {
return;
}
const gpuContext: WebGPUDeviceContext = CK.MakeGPUDeviceContext(device)!; // $ExpectType GrDirectContext
// Texture surface.
const surface1 = CK.MakeGPUTextureSurface(gpuContext, texture, 800, 600, // $ExpectType Surface | null
CK.ColorSpace.SRGB);
// Canvas surfaces.
const canvasContext = CK.MakeGPUCanvasContext(gpuContext, canvas, { // $ExpectType WebGPUCanvasContext
format: "bgra8unorm",
alphaMode: "premultiplied",
})!;
canvasContext.requestAnimationFrame((canvas: Canvas) => {
canvas.clear([0, 0, 0, 0]);
});
const surface2 = CK.MakeGPUCanvasSurface(canvasContext, CK.ColorSpace.SRGB); // $ExpectType Surface | null
const surface3 = CK.MakeGPUCanvasSurface(canvasContext, CK.ColorSpace.SRGB, 10, 10); // $ExpectType Surface | null
}

Sorry, the diff of this file is too big to display

This folder contains Typescript function definitions and documentation.
The goal will be to keep these up to date and contribute them to the
DefinitelyTyped project.
{
"compilerOptions": {
"module": "commonjs",
"lib": ["es6", "dom", "es2017" ],
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noEmit": true,
"baseUrl": ".",
"paths": { "canvaskit-wasm": ["."] },
"typeRoots": [ "./node_modules/@webgpu/types", "./node_modules/@types" ]
}
}
{
"extends": "dtslint/dtslint.json",
"rules": {
"no-bad-reference": true,
"no-declare-current-package": true,
"no-self-import": true,
"no-outside-dependencies": true,
"no-redundant-jsdoc": false,
"no-redundant-jsdoc-2": true,
"npm-naming": [true, { "mode": "code" }]
}
}
+5
-4
{
"name": "@pencil.dev/cli",
"version": "0.2.2",
"version": "0.2.3",
"description": "CLI tool for running the Pencil AI agent manipulating .pen design files",

@@ -11,9 +11,11 @@ "type": "module",

"scripts": {
"build": "tsdown && npm run bundle:mcp",
"build": "tsdown && npm run bundle:mcp && npm run bundle:skia",
"build:prod": "npm run build && node scripts/obfuscate.js",
"bundle:mcp": "mkdir -p dist/out && cp ../../servers/mcp/mcp-server-* dist/out/ && chmod +x dist/out/mcp-server-*",
"bundle:skia": "node scripts/copy-pencil-skia.mjs",
"dev": "npm run build && cross-env NODE_ENV=development npm run start",
"start": "node dist/index.cjs",
"lint": "biome check",
"lint:ci": "biome ci"
"lint:ci": "biome ci",
"test:package": "bash scripts/test-package-docker.sh"
},

@@ -31,3 +33,2 @@ "keywords": [

"@anthropic-ai/claude-agent-sdk": "^0.2.72",
"@highagency/pencil-skia": "^0.47.0",
"@inquirer/prompts": "^8.3.0",

@@ -34,0 +35,0 @@ "eventemitter3": "^5.0.1",

Sorry, the diff of this file is too big to display