@pencil.dev/cli
Advanced tools
| <!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. <video>) | ||
| ### 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
Explicitly Unlicensed Item
LicenseSomething was found which is explicitly marked as unlicensed.
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Network access
Supply chain riskThis module accesses the network.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Explicitly Unlicensed Item
LicenseSomething was found which is explicitly marked as unlicensed.
58345662
20.37%6
-14.29%43
138.89%14871
117.79%4
33.33%7
75%10
42.86%- Removed