[canvaskit] JS API in place for shaped text

Depends on https://skia-review.googlesource.com/c/skia/+/186870

It's optional at build time, which is good given that
it adds about 2MB of uncompressed size (from 4.3 MB to 6.4 MB)

Bug: skia:
Change-Id: I5f54ad628b735c3bc880e917394fb27d16849ebe
Reviewed-on: https://skia-review.googlesource.com/c/187924
Commit-Queue: Kevin Lubick <kjlubick@google.com>
Reviewed-by: Ben Wagner <bungeman@google.com>
Reviewed-by: Florin Malita <fmalita@chromium.org>
This commit is contained in:
Kevin Lubick 2019-02-22 10:04:06 -05:00 committed by Skia Commit-Bot
parent 18e5841c0d
commit 1ba9c4df77
26 changed files with 492 additions and 154 deletions

View File

@ -12,11 +12,6 @@ if (is_fuchsia) {
import("//build/vulkan/config.gni")
}
if (!defined(is_skia_standalone)) {
is_skia_standalone = false
}
is_skia_dev_build = is_skia_standalone && !is_official_build
declare_args() {
skia_enable_flutter_defines = false
}
@ -45,12 +40,10 @@ declare_args() {
skia_enable_ccpr = true
skia_enable_nvpr = !skia_enable_flutter_defines
skia_enable_discrete_gpu = true
skia_enable_gpu = true
skia_enable_nima = false
skia_enable_pdf = true
skia_enable_spirv_validation = is_skia_dev_build && is_debug
skia_enable_skpicture = true
skia_enable_tools = is_skia_dev_build
skia_enable_vulkan_debug_layers = is_skia_dev_build && is_debug
skia_qt_path = getenv("QT_PATH")
skia_compile_processors = false
@ -93,9 +86,6 @@ if (defined(skia_settings)) {
import(skia_settings)
}
# Our tools require static linking (they use non-exported symbols), and the GPU backend.
skia_enable_tools = skia_enable_tools && !is_component_build && skia_enable_gpu
skia_public_includes = [
"include/android",
"include/c",
@ -1125,6 +1115,13 @@ static_library("pathkit") {
]
}
group("modules") {
deps = [
"modules/skottie",
"modules/skshaper",
]
}
# Targets guarded by skia_enable_tools may use //third_party freely.
if (skia_enable_tools) {
# Used by gn_to_bp.py to list our public include dirs.

View File

@ -13,9 +13,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
`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.
- `SkCanvas.drawText()` now requires an `SkFont` object for raw strings.
### Removed
- `SkPaint.setTextSize()`, `SkPaint.getTextSize()`, `SkPaint.setTypeface()`

View File

@ -61,3 +61,10 @@ test-continuous:
node-example:
node ./canvaskit/node.example.js --expose-wasm
docker-compile:
mkdir -p ${SKIA_ROOT}/out/canvaskit_wasm_docker
docker run --rm --volume ${SKIA_ROOT}:/SRC \
--volume ${SKIA_ROOT}/out/canvaskit_wasm_docker:/OUT \
gcr.io/skia-public/canvaskit-emsdk:1.38.27_v1 \
/SRC/infra/canvaskit/build_canvaskit.sh

Binary file not shown.

View File

@ -43,6 +43,9 @@
<canvas id=paths width=200 height=200></canvas>
<canvas id=ink width=300 height=300></canvas>
<h2> CanvasKit can allow for text shaping (e.g. breaking, kerning)</h2>
<canvas id=shape1 width=600 height=600></canvas>
<h2> Skottie </h2>
<canvas id=sk_legos width=300 height=300></canvas>
<canvas id=sk_drinks width=500 height=500></canvas>
@ -61,6 +64,7 @@
var fullBounds = {fLeft: 0, fTop: 0, fRight: 500, fBottom: 500};
var robotoData = null;
var notoserifData = null;
var bonesImageData = null;
CanvasKitInit({
@ -85,12 +89,14 @@
CanvasAPI5(CanvasKit);
CanvasAPI6(CanvasKit);
CanvasAPI7(CanvasKit);
CanvasAPI8(CanvasKit)
CanvasAPI8(CanvasKit);
VertexAPI1(CanvasKit);
VertexAPI2(CanvasKit, bonesImageData);
GradiantAPI1(CanvasKit);
TextShapingAPI1(CanvasKit, notoserifData);
});
fetch('https://storage.googleapis.com/skia-cdn/misc/lego_loader.json').then((resp) => {
@ -132,7 +138,14 @@
fetch('./Roboto-Regular.woff').then((resp) => {
resp.arrayBuffer().then((buffer) => {
robotoData = buffer;
DrawingExample(CanvasKit, robotoData)
DrawingExample(CanvasKit, robotoData);
});
});
fetch('./NotoSerif-Regular.ttf').then((resp) => {
resp.arrayBuffer().then((buffer) => {
notoserifData = buffer;
TextShapingAPI1(CanvasKit, notoserifData);
});
});
@ -152,7 +165,7 @@
const paint = new CanvasKit.SkPaint();
const fontMgr = CanvasKit.SkFontMgr.RefDefault();
let roboto = fontMgr.MakeTypefaceFromData(robotoData);
const roboto = fontMgr.MakeTypefaceFromData(robotoData);
const textPaint = new CanvasKit.SkPaint();
textPaint.setColor(CanvasKit.RED);
@ -183,7 +196,7 @@
canvas.clear(CanvasKit.TRANSPARENT);
canvas.drawPath(path, paint);
canvas.drawText('Try Clicking!', 10, 280, textFont, textPaint);
canvas.drawText('Try Clicking!', 10, 280, textPaint, textFont);
surface.flush();
@ -1078,4 +1091,73 @@
textBlob.delete();
surface.flush();
}
function TextShapingAPI1(CanvasKit, notoserifData) {
if (!notoserifData || !CanvasKit) {
return;
}
const surface = CanvasKit.MakeCanvasSurface('shape1');
if (!surface) {
console.error('Could not make surface');
return;
}
const context = CanvasKit.currentContext();
const canvas = surface.getCanvas();
const paint = new CanvasKit.SkPaint();
paint.setColor(CanvasKit.BLUE);
paint.setStyle(CanvasKit.PaintStyle.Stroke);
const fontMgr = CanvasKit.SkFontMgr.RefDefault();
const notoSerif = fontMgr.MakeTypefaceFromData(notoserifData);
const textPaint = new CanvasKit.SkPaint();
const textFont = new CanvasKit.SkFont(notoSerif, 20);
canvas.drawRect(CanvasKit.LTRBRect(30, 30, 200, 200), paint);
canvas.drawText('This text is not shaped, and overflows the boundry',
35, 50, textPaint, textFont);
const shapedText = new CanvasKit.ShapedText({
font: textFont,
leftToRight: true,
text: 'This text *is* shaped, and wraps to the right width.',
width: 160,
});
const textBoxX = 35;
const textBoxY = 55;
canvas.drawText(shapedText, textBoxX, textBoxY, textPaint);
const bounds = shapedText.getBounds();
bounds.fLeft += textBoxX;
bounds.fRight += textBoxX;
bounds.fTop += textBoxY;
bounds.fBottom += textBoxY
canvas.drawRect(bounds, paint);
const SHAPE_TEST_TEXT = 'VAVAVAVAVAFIfi';
const textFont2 = new CanvasKit.SkFont(notoSerif, 60);
const shapedText2 = new CanvasKit.ShapedText({
font: textFont2,
leftToRight: true,
text: SHAPE_TEST_TEXT,
width: 600,
});
canvas.drawText('no kerning ↓', 10, 240, textPaint, textFont);
canvas.drawText(SHAPE_TEST_TEXT, 10, 300, textPaint, textFont2);
canvas.drawText(shapedText2, 10, 300, textPaint);
canvas.drawText('kerning ↑', 10, 390, textPaint, textFont);
surface.flush();
paint.delete();
notoSerif.delete();
textPaint.delete();
textFont.delete();
shapedText.delete();
textFont2.delete();
shapedText2.delete();
surface.delete();
}
</script>

View File

@ -73,7 +73,7 @@ function fancyAPI(CanvasKit) {
canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
canvas.drawPath(skpath, paint);
canvas.drawText('Try Clicking!', 10, 280, textFont, textPaint);
canvas.drawText('Try Clicking!', 10, 280, textPaint, textFont);
surface.flush();

View File

@ -40,6 +40,7 @@
#include "SkScalar.h"
#include "SkShader.h"
#include "SkShadowUtils.h"
#include "SkShaper.h"
#include "SkString.h"
#include "SkStrokeRec.h"
#include "SkSurface.h"
@ -462,6 +463,58 @@ Uint8Array getSkDataBytes(const SkData *data) {
return Uint8Array(typed_memory_view(data->size(), data->bytes()));
}
// Text Shaping abstraction
struct ShapedTextOpts {
SkFont font;
bool leftToRight;
std::string text;
SkScalar width;
};
std::unique_ptr<SkShaper> shaper;
static sk_sp<SkTextBlob> do_shaping(const ShapedTextOpts& opts, SkPoint* pt) {
SkTextBlobBuilderRunHandler builder(opts.text.c_str());
if (!shaper) {
shaper = SkShaper::Make();
}
*pt = shaper->shape(&builder, opts.font, opts.text.c_str(),
opts.text.length(), opts.leftToRight,
{0, 0}, opts.width);
return builder.makeBlob();
}
class ShapedText {
public:
ShapedText(ShapedTextOpts opts) : fOpts(opts) {}
SkRect getBounds() {
this->init();
return SkRect::MakeLTRB(0, 0, fOpts.width, fPoint.y());
}
SkTextBlob* blob() {
this->init();
return fBlob.get();
}
private:
const ShapedTextOpts fOpts;
SkPoint fPoint;
sk_sp<SkTextBlob> fBlob;
void init() {
if (!fBlob) {
fBlob = do_shaping(fOpts, &fPoint);
}
}
};
void drawShapedText(SkCanvas& canvas, ShapedText st, SkScalar x,
SkScalar y, SkPaint paint) {
canvas.drawTextBlob(st.blob(), x, y, paint);
}
// These objects have private destructors / delete mthods - I don't think
// we need to do anything other than tell emscripten to do nothing.
namespace emscripten {
@ -690,6 +743,7 @@ EMSCRIPTEN_BINDINGS(Skia) {
SkShadowUtils::DrawShadow(&self, path, zPlaneParams, lightPos, lightRadius,
SkColor(ambientColor), SkColor(spotColor), flags);
}))
.function("_drawShapedText", &drawShapedText)
.function("_drawSimpleText", optional_override([](SkCanvas& self, uintptr_t /* char* */ sptr,
size_t len, SkScalar x, SkScalar y, const SkFont& font,
const SkPaint& paint) {
@ -753,6 +807,10 @@ EMSCRIPTEN_BINDINGS(Skia) {
.function("setSkewX", &SkFont::setSkewX)
.function("setTypeface", &SkFont::setTypeface, allow_raw_pointers());
class_<ShapedText>("ShapedText")
.constructor<ShapedTextOpts>()
.function("getBounds", &ShapedText::getBounds);
class_<SkFontMgr>("SkFontMgr")
.smart_ptr<sk_sp<SkFontMgr>>("sk_sp<SkFontMgr>")
.class_function("RefDefault", &SkFontMgr::RefDefault)
@ -1043,6 +1101,12 @@ EMSCRIPTEN_BINDINGS(Skia) {
// A value object is much simpler than a class - it is returned as a JS
// object and does not require delete().
// https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/embind.html#value-types
value_object<ShapedTextOpts>("ShapedTextOpts")
.field("font", &ShapedTextOpts::font)
.field("leftToRight", &ShapedTextOpts::leftToRight)
.field("text", &ShapedTextOpts::text)
.field("width", &ShapedTextOpts::width);
value_object<SkRect>("SkRect")
.field("fLeft", &SkRect::fLeft)
.field("fTop", &SkRect::fTop)

View File

@ -51,38 +51,32 @@ if [[ $@ == *cpu* ]]; then
WASM_GPU="-DSK_SUPPORT_GPU=0 --pre-js $BASE_DIR/cpu.js"
fi
WASM_SKOTTIE=" \
$BASE_DIR/skottie_bindings.cpp \
modules/skottie/src/Skottie.cpp \
modules/skottie/src/SkottieAdapter.cpp \
modules/skottie/src/SkottieAnimator.cpp \
modules/skottie/src/SkottieJson.cpp \
modules/skottie/src/SkottieLayer.cpp \
modules/skottie/src/SkottieLayerEffect.cpp \
modules/skottie/src/SkottiePrecompLayer.cpp \
modules/skottie/src/SkottieProperty.cpp \
modules/skottie/src/SkottieShapeLayer.cpp \
modules/skottie/src/SkottieTextLayer.cpp \
modules/skottie/src/SkottieValue.cpp \
modules/sksg/src/*.cpp \
modules/skshaper/src/SkShaper.cpp \
modules/skshaper/src/SkShaper_primitive.cpp \
# TODO(fmalita,kjlubick): reduce this list to one item by fixing
# the libskottie.a and libsksg.a builds
SKOTTIE_BINDINGS="$BASE_DIR/skottie_bindings.cpp\
src/core/SkColorMatrixFilterRowMajor255.cpp \
src/core/SkCubicMap.cpp \
src/core/SkTime.cpp \
src/effects/imagefilters/SkDropShadowImageFilter.cpp \
src/pathops/SkOpBuilder.cpp \
src/utils/SkJSON.cpp \
src/utils/SkParse.cpp "
SKOTTIE_LIB="$BUILD_DIR/libskottie.a \
$BUILD_DIR/libsksg.a"
if [[ $@ == *no_skottie* ]]; then
echo "Omitting Skottie"
WASM_SKOTTIE=""
SKOTTIE_LIB=""
SKOTTIE_BINDINGS=""
fi
WASM_MANAGED_SKOTTIE="\
MANAGED_SKOTTIE_BINDINGS="\
-DSK_INCLUDE_MANAGED_SKOTTIE=1 \
modules/skottie/utils/SkottieUtils.cpp"
if [[ $@ == *no_managed_skottie* ]]; then
echo "Omitting managed Skottie"
WASM_MANAGED_SKOTTIE="-DSK_INCLUDE_MANAGED_SKOTTIE=0"
MANAGED_SKOTTIE_BINDINGS="-DSK_INCLUDE_MANAGED_SKOTTIE=0"
fi
HTML_CANVAS_API="--pre-js $BASE_DIR/htmlcanvas/preamble.js \
@ -115,6 +109,17 @@ else
--align 4
fi
GN_SHAPER="skia_use_icu=true skia_use_system_icu=false"
SHAPER_LIB="$BUILD_DIR/libharfbuzz.a \
$BUILD_DIR/libicu.a"
SHAPER_TARGETS="libharfbuzz.a libicu.a"
if [[ $@ == *primitive_shaper* ]]; then
echo "Using the primitive shaper instead of the harfbuzz/icu one"
GN_SHAPER="skia_use_icu=false"
SHAPER_LIB=""
SHAPER_TARGETS=""
fi
# Turn off exiting while we check for ninja (which may not be on PATH)
set +e
NINJA=`which ninja`
@ -149,33 +154,36 @@ echo "Compiling bitcode"
skia_use_expat=false \
skia_use_fontconfig=false \
skia_use_freetype=true \
skia_use_icu=false \
skia_use_libheif=false \
skia_use_system_libjpeg_turbo = false \
skia_use_libjpeg_turbo=true \
skia_use_libpng=true \
skia_use_libwebp=false \
skia_use_lua=false \
skia_use_piex=false \
skia_use_system_libpng=true \
skia_use_system_freetype2=true \
skia_use_system_libjpeg_turbo = false \
skia_use_vulkan=false \
skia_use_zlib=true \
\
${GN_SHAPER} \
${GN_GPU} \
\
skia_enable_skshaper=true \
skia_enable_ccpr=false \
skia_enable_nvpr=false \
skia_enable_skpicture=false \
${GN_GPU} \
skia_enable_fontmgr_empty=false \
skia_enable_pdf=false"
${NINJA} -C ${BUILD_DIR} libskia.a
# Build all the libs, we'll link the appropriate ones down below
${NINJA} -C ${BUILD_DIR} libskia.a libskottie.a libsksg.a libskshaper.a $SHAPER_TARGETS
export EMCC_CLOSURE_ARGS="--externs $BASE_DIR/externs.js "
echo "Generating final wasm"
# Skottie doesn't end up in libskia and is currently not its own library
# so we just hack in the .cpp files we need for now.
# Emscripten prefers that libskia.a goes last in order, otherwise, it
# Emscripten prefers that the .a files go last in order, otherwise, it
# may drop symbols that it incorrectly thinks aren't used. One day,
# Emscripten will use LLD, which may relax this requirement.
${EMCXX} \
@ -200,8 +208,8 @@ ${EMCXX} \
-Isrc/sfnt/ \
-Isrc/shaders/ \
-Isrc/utils/ \
-Ithird_party/icu \
-Itools \
-Itools/fonts \
-DSK_DISABLE_READBUFFER \
-DSK_DISABLE_AAA \
-DSK_DISABLE_DAA \
@ -214,16 +222,19 @@ ${EMCXX} \
$HTML_CANVAS_API \
$BUILTIN_FONT \
$BASE_DIR/canvaskit_bindings.cpp \
$WASM_SKOTTIE \
$WASM_MANAGED_SKOTTIE \
$SKOTTIE_BINDINGS \
$MANAGED_SKOTTIE_BINDINGS \
$BUILD_DIR/libskia.a \
$SKOTTIE_LIB \
$BUILD_DIR/libskshaper.a \
$SHAPER_LIB \
-s ALLOW_MEMORY_GROWTH=1 \
-s EXPORT_NAME="CanvasKitInit" \
-s FORCE_FILESYSTEM=0 \
-s MODULARIZE=1 \
-s NO_EXIT_RUNTIME=1 \
-s STRICT=1 \
-s TOTAL_MEMORY=32MB \
-s TOTAL_MEMORY=128MB \
-s USE_FREETYPE=1 \
-s USE_LIBPNG=1 \
-s WARN_UNALIGNED=1 \

View File

@ -67,6 +67,7 @@ var CanvasKit = {
_MakeSkVertices: function() {},
_MakeTwoPointConicalGradientShader: function() {},
_decodeImage: function() {},
_drawShapedText: function() {},
_getRasterDirectSurface: function() {},
_getRasterN32PremulSurface: function() {},
_getWebGLSurface: function() {},
@ -77,6 +78,11 @@ var CanvasKit = {
// Objects and properties on CanvasKit
ShapedText: {
// public API (from C++ bindings)
getBounds: function() {},
},
SkCanvas: {
// public API (from C++ bindings)
clear: function() {},

View File

@ -382,15 +382,20 @@
throw 'encodeToData expected to take 0 or 2 arguments. Got ' + arguments.length;
}
CanvasKit.SkCanvas.prototype.drawText = function(str, x, y, font, paint) {
// lengthBytesUTF8 and stringToUTF8Array are defined in the emscripten
// JS. See https://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html#stringToUTF8
// Add 1 for null terminator
var strLen = lengthBytesUTF8(str) + 1;
var strPtr = CanvasKit._malloc(strLen);
// Add 1 for the null terminator.
stringToUTF8(str, strPtr, strLen);
this._drawSimpleText(strPtr, strLen, x, y, font, paint);
// str can be either a text string or a ShapedText object
CanvasKit.SkCanvas.prototype.drawText = function(str, x, y, paint, font) {
if (typeof str === 'string') {
// lengthBytesUTF8 and stringToUTF8Array are defined in the emscripten
// JS. See https://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html#stringToUTF8
// Add 1 for null terminator
var strLen = lengthBytesUTF8(str) + 1;
var strPtr = CanvasKit._malloc(strLen);
stringToUTF8(str, strPtr, strLen);
this._drawSimpleText(strPtr, strLen, x, y, font, paint);
} else {
this._drawShapedText(str, x, y, paint);
}
}
// returns Uint8Array

View File

@ -14,6 +14,7 @@ module.exports = function(config) {
{ pattern: 'tests/assets/*', included:false, served:true},
'../../modules/pathkit/tests/testReporter.js',
'canvaskit/bin/canvaskit.js',
'tests/util.js',
'tests/*.spec.js'
],

View File

@ -517,9 +517,9 @@ describe('CanvasKit\'s Canvas 2d Behavior', function() {
it('supports gradients, which respect clip/save/restore', function(done) {
LoadCanvasKit.then(catchException(done, () => {
multipleCanvasTest('gradients_clip', done, (canvas) => {
let ctx = canvas.getContext('2d');
const ctx = canvas.getContext('2d');
var rgradient = ctx.createRadialGradient(200, 300, 10, 100, 100, 300);
const rgradient = ctx.createRadialGradient(200, 300, 10, 100, 100, 300);
rgradient.addColorStop(0, 'red');
rgradient.addColorStop(.7, 'white');
@ -539,7 +539,7 @@ describe('CanvasKit\'s Canvas 2d Behavior', function() {
ctx.save();
ctx.clip();
var lgradient = ctx.createLinearGradient(200, 20, 420, 40);
const lgradient = ctx.createLinearGradient(200, 20, 420, 40);
lgradient.addColorStop(0, 'green');
lgradient.addColorStop(.5, 'cyan');
@ -844,8 +844,8 @@ describe('CanvasKit\'s Canvas 2d Behavior', function() {
LoadCanvasKit.then(catchException(done, () => {
multipleCanvasTest('path2d_line_drawing_operations', done, (canvas) => {
let ctx = canvas.getContext('2d');
var clock;
var path;
let clock;
let path;
if (canvas.makePath2D) {
clock = canvas.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');
path = canvas.makePath2D();

View File

@ -0,0 +1,118 @@
// The increased timeout is especially needed with larger binaries
// like in the debug/gpu build
jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
describe('CanvasKit\'s Path Behavior', function() {
// Note, don't try to print the CanvasKit object - it can cause Karma/Jasmine to lock up.
var CanvasKit = null;
const LoadCanvasKit = new Promise(function(resolve, reject) {
if (CanvasKit) {
resolve();
} else {
CanvasKitInit({
locateFile: (file) => '/canvaskit/'+file,
}).ready().then((_CanvasKit) => {
CanvasKit = _CanvasKit;
resolve();
});
}
});
let container = document.createElement('div');
document.body.appendChild(container);
const CANVAS_WIDTH = 600;
const CANVAS_HEIGHT = 600;
beforeEach(function() {
container.innerHTML = `
<canvas width=600 height=600 id=test></canvas>
<canvas width=600 height=600 id=report></canvas>`;
});
afterEach(function() {
container.innerHTML = '';
});
it('can draw shaped and unshaped text', function(done) {
let fontBuffer = null;
// This font is known to support kerning
const skFontLoaded = fetch('/assets/NotoSerif-Regular.ttf').then(
(response) => response.arrayBuffer()).then(
(buffer) => {
fontBuffer = buffer;
});
LoadCanvasKit.then(catchException(done, () => {
skFontLoaded.then(() => {
// This is taken from example.html
const surface = CanvasKit.MakeCanvasSurface('test');
expect(surface).toBeTruthy('Could not make surface')
if (!surface) {
done();
return;
}
const canvas = surface.getCanvas();
const paint = new CanvasKit.SkPaint();
paint.setColor(CanvasKit.BLUE);
paint.setStyle(CanvasKit.PaintStyle.Stroke);
const fontMgr = CanvasKit.SkFontMgr.RefDefault();
const notoSerif = fontMgr.MakeTypefaceFromData(fontBuffer);
const textPaint = new CanvasKit.SkPaint();
// use the built-in monospace typeface.
const textFont = new CanvasKit.SkFont(notoSerif, 20);
canvas.drawRect(CanvasKit.LTRBRect(30, 30, 200, 200), paint);
canvas.drawText('This text is not shaped, and overflows the boundry',
35, 50, textPaint, textFont);
const shapedText = new CanvasKit.ShapedText({
font: textFont,
leftToRight: true,
text: 'This text *is* shaped, and wraps to the right width.',
width: 160,
});
const textBoxX = 35;
const textBoxY = 55;
canvas.drawText(shapedText, textBoxX, textBoxY, textPaint);
const bounds = shapedText.getBounds();
bounds.fLeft += textBoxX;
bounds.fRight += textBoxX;
bounds.fTop += textBoxY;
bounds.fBottom += textBoxY
canvas.drawRect(bounds, paint);
const SHAPE_TEST_TEXT = 'VAVAVAVAVAFIfi';
const textFont2 = new CanvasKit.SkFont(notoSerif, 60);
const shapedText2 = new CanvasKit.ShapedText({
font: textFont2,
leftToRight: true,
text: SHAPE_TEST_TEXT,
width: 600,
});
canvas.drawText('no kerning ↓', 10, 240, textPaint, textFont);
canvas.drawText(SHAPE_TEST_TEXT, 10, 300, textPaint, textFont2);
canvas.drawText(shapedText2, 10, 300, textPaint);
canvas.drawText('kerning ↑', 10, 390, textPaint, textFont);
surface.flush();
paint.delete();
notoSerif.delete();
textPaint.delete();
textFont.delete();
shapedText.delete();
textFont2.delete();
shapedText2.delete();
reportSurface(surface, 'text_shaping', done);
});
}));
});
// TODO more tests
});

View File

@ -33,22 +33,6 @@ describe('CanvasKit\'s Path Behavior', function() {
container.innerHTML = '';
});
function reportSurface(surface, testname, done) {
// In docker, the webgl canvas is blank, but the surface has the pixel
// data. So, we copy it out and draw it to a normal canvas to take a picture.
// To be consistent across CPU and GPU, we just do it for all configurations
// (even though the CPU canvas shows up after flush just fine).
let pixels = surface.getCanvas().readPixels(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
pixels = new Uint8ClampedArray(pixels.buffer);
var imageData = new ImageData(pixels, CANVAS_WIDTH, CANVAS_HEIGHT);
let reportingCanvas = document.getElementById('report');
reportingCanvas.getContext('2d').putImageData(imageData, 0, 0);
reportCanvas(reportingCanvas, testname).then(() => {
done();
}).catch(reportError(done));
}
it('can draw a path', function(done) {
LoadCanvasKit.then(catchException(done, () => {
// This is taken from example.html
@ -134,7 +118,7 @@ describe('CanvasKit\'s Path Behavior', function() {
canvas.drawArc(CanvasKit.LTRBRect(55, 35, 95, 80), 15, 270, true, paint);
const font = new CanvasKit.SkFont(null, 20);
canvas.drawText('this is ascii text', 5, 100, font, paint);
canvas.drawText('this is ascii text', 5, 100, paint, font);
const blob = CanvasKit.SkTextBlob.MakeFromText('Unicode chars 💩 é É ص', font);
canvas.drawTextBlob(blob, 5, 130, paint);
@ -189,7 +173,7 @@ describe('CanvasKit\'s Path Behavior', function() {
canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
canvas.drawPath(path, paint);
canvas.drawText('This is text', 10, 280, textFont, textPaint);
canvas.drawText('This is text', 10, 280, textPaint, textFont);
surface.flush();
dpe.delete();
path.delete();

View File

@ -0,0 +1,19 @@
// The size of the golden images (DMs)
const CANVAS_WIDTH = 600;
const CANVAS_HEIGHT = 600;
function reportSurface(surface, testname, done) {
// In docker, the webgl canvas is blank, but the surface has the pixel
// data. So, we copy it out and draw it to a normal canvas to take a picture.
// To be consistent across CPU and GPU, we just do it for all configurations
// (even though the CPU canvas shows up after flush just fine).
let pixels = surface.getCanvas().readPixels(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
pixels = new Uint8ClampedArray(pixels.buffer);
const imageData = new ImageData(pixels, CANVAS_WIDTH, CANVAS_HEIGHT);
let reportingCanvas = document.getElementById('report');
reportingCanvas.getContext('2d').putImageData(imageData, 0, 0);
reportCanvas(reportingCanvas, testname).then(() => {
done();
}).catch(reportError(done));
}

View File

@ -2,7 +2,17 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
declare_args() {
skia_use_icu = !is_fuchsia && !is_ios
skia_enable_skshaper = true
if (!defined(is_skia_standalone)) {
is_skia_standalone = false
}
is_skia_dev_build = is_skia_standalone && !is_official_build
declare_args() {
skia_enable_gpu = true
skia_enable_skshaper = true
skia_enable_tools = is_skia_dev_build
skia_use_icu = !is_fuchsia && !is_ios
}
# Our tools require static linking (they use non-exported symbols), and the GPU backend.
skia_enable_tools = skia_enable_tools && !is_component_build && skia_enable_gpu

View File

@ -2,7 +2,7 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
DOCKER_IMAGE = 'gcr.io/skia-public/emsdk-release:1.38.16_v1'
DOCKER_IMAGE = 'gcr.io/skia-public/canvaskit-emsdk:1.38.27_v1'
INNER_BUILD_SCRIPT = '/SRC/skia/infra/canvaskit/build_canvaskit.sh'
BUILD_PRODUCTS_ISOLATE_WHITELIST_WASM = [

View File

@ -23,7 +23,7 @@
"[START_DIR]/cache/work:/SRC",
"--volume",
"[START_DIR]/cache/docker/canvaskit:/OUT",
"gcr.io/skia-public/emsdk-release:1.38.16_v1",
"gcr.io/skia-public/canvaskit-emsdk:1.38.27_v1",
"/SRC/skia/infra/canvaskit/build_canvaskit.sh",
"debug"
],

View File

@ -23,7 +23,7 @@
"[START_DIR]/cache/work:/SRC",
"--volume",
"[START_DIR]/cache/docker/canvaskit:/OUT",
"gcr.io/skia-public/emsdk-release:1.38.16_v1",
"gcr.io/skia-public/canvaskit-emsdk:1.38.27_v1",
"/SRC/skia/infra/canvaskit/build_canvaskit.sh",
"cpu"
],

View File

@ -0,0 +1,6 @@
EMSDK_VERSION=1.38.27_v1
publish_canvaskit_emsdk:
docker build -t canvaskit-emsdk ./canvaskit-emsdk/
docker tag emsdk-base gcr.io/skia-public/canvaskit-emsdk:${EMSDK_VERSION}
docker push gcr.io/skia-public/canvaskit-emsdk:${EMSDK_VERSION}

View File

@ -0,0 +1,7 @@
# A Docker image that augments the Emscripten SDK Docker image
# with anything needed to build Canvaskit
FROM gcr.io/skia-public/emsdk-release:1.38.27_v1
RUN apt-get update && apt-get upgrade -y && apt-get install -y \
libfreetype6-dev

View File

@ -3,6 +3,8 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("../../gn/skia.gni")
declare_args() {
skia_enable_skottie = true
}
@ -14,7 +16,7 @@ config("public_config") {
}
}
source_set("skottie") {
component("skottie") {
if (skia_enable_skottie) {
import("skottie.gni")
public_configs = [ ":public_config" ]
@ -49,93 +51,95 @@ if (defined(is_skia_standalone)) {
}
}
source_set("tests") {
if (skia_enable_skottie) {
testonly = true
if (skia_enable_tools) {
source_set("tests") {
if (skia_enable_skottie) {
testonly = true
configs += [
"../..:skia_private",
"../..:tests_config",
]
sources = [
"src/SkottieTest.cpp",
]
configs += [
"../..:skia_private",
"../..:tests_config",
]
sources = [
"src/SkottieTest.cpp",
]
deps = [
":skottie",
"../..:gpu_tool_utils",
"../..:skia",
]
deps = [
":skottie",
"../..:gpu_tool_utils",
"../..:skia",
]
}
}
}
source_set("fuzz") {
if (skia_enable_skottie) {
source_set("fuzz") {
if (skia_enable_skottie) {
testonly = true
configs += [ "../..:skia_private" ]
include_dirs = [
"../../tools",
"../../tools/flags",
"../../tools/fonts",
]
sources = [
"../../tools/Resources.cpp",
"../../tools/fonts/SkTestFontMgr.cpp",
"../../tools/fonts/SkTestSVGTypeface.cpp",
"../../tools/fonts/SkTestTypeface.cpp",
"fuzz/FuzzSkottieJSON.cpp",
]
deps = [
"../..:experimental_svg_model",
"../..:skia",
]
public_deps = [
":skottie",
]
}
}
source_set("tool") {
testonly = true
configs += [ "../..:skia_private" ]
include_dirs = [
"../../tools",
"../../tools/flags",
"../../tools/fonts",
]
sources = [
"../../tools/Resources.cpp",
"../../tools/fonts/SkTestFontMgr.cpp",
"../../tools/fonts/SkTestSVGTypeface.cpp",
"../../tools/fonts/SkTestTypeface.cpp",
"fuzz/FuzzSkottieJSON.cpp",
"src/SkottieTool.cpp",
]
deps = [
"../..:experimental_svg_model",
"../..:flags",
"../..:skia",
]
public_deps = [
":skottie",
":utils",
]
}
}
source_set("tool") {
testonly = true
source_set("gm") {
if (skia_enable_skottie) {
testonly = true
configs += [ "../..:skia_private" ]
sources = [
"src/SkottieTool.cpp",
]
# would be nice to have a gm_config
include_dirs = [ "../../gm" ]
deps = [
"../..:flags",
"../..:skia",
]
configs += [ "../..:skia_private" ]
sources = [
"gm/SkottieGM.cpp",
]
public_deps = [
":skottie",
":utils",
]
}
source_set("gm") {
if (skia_enable_skottie) {
testonly = true
# would be nice to have a gm_config
include_dirs = [ "../../gm" ]
configs += [ "../..:skia_private" ]
sources = [
"gm/SkottieGM.cpp",
]
deps = [
":skottie",
":utils",
"../..:gpu_tool_utils",
"../..:skia",
"../..:tool_utils",
]
deps = [
":skottie",
":utils",
"../..:gpu_tool_utils",
"../..:skia",
"../..:tool_utils",
]
}
}
}
}

View File

@ -3,6 +3,8 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("../../gn/skia.gni")
config("public_config") {
include_dirs = [ "include" ]
}
@ -17,7 +19,7 @@ component("sksg") {
]
}
if (defined(is_skia_standalone)) {
if (defined(is_skia_standalone) && skia_enable_tools) {
source_set("tests") {
testonly = true

View File

@ -15,7 +15,7 @@ config("public_config") {
}
}
source_set("skshaper") {
component("skshaper") {
if (skia_enable_skshaper) {
import("skshaper.gni")
public_configs = [ ":public_config" ]

View File

@ -25,6 +25,8 @@ if (skia_use_icu) {
}
data_dir = "../externals/icu/"
if (target_cpu == "wasm") {
# Use a super super super stripped down version for wasm,
# which is the same thing flutter is using.
data_dir += "flutter"
} else if (is_android) {
data_dir += "android"
@ -37,8 +39,12 @@ if (skia_use_icu) {
if (target_cpu == "wasm") {
_u_icu_version_major_num = "63" # defined in source/common/unicode/uvernum.h
script = "make_data_cpp.py"
inputs = [ "$data_dir/icudtl.dat" ]
outputs = [ data_assembly ]
inputs = [
"$data_dir/icudtl.dat",
]
outputs = [
data_assembly,
]
args = [
"icudt${_u_icu_version_major_num}_dat",
rebase_path(inputs[0], root_build_dir),
@ -80,6 +86,11 @@ if (skia_use_icu) {
"U_ENABLE_DYLOAD=0",
"U_I18N_IMPLEMENTATION",
]
if (target_cpu == "wasm") {
# Tell ICU that we are a 32 bit platform, otherwise,
# double-conversion-utils.h doesn't know how to operate.
defines += [ "__i386__" ]
}
sources = icu_sources
if (is_win) {
deps = [