[canvaskit] Initial addition of SkParagraph
There are more parts of ParagraphStyle and TextStyle, but this should be a bulk of the components. Bug: skia:9469 Change-Id: I87fff6700f41cff49ecbee3a1339e84c36699c93 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/244837 Reviewed-by: Julia Lavrova <jlavrova@google.com>
This commit is contained in:
parent
c95c4a69e1
commit
369f6a5ea2
1
BUILD.gn
1
BUILD.gn
@ -1072,6 +1072,7 @@ group("modules") {
|
|||||||
deps = [
|
deps = [
|
||||||
"modules/particles",
|
"modules/particles",
|
||||||
"modules/skottie",
|
"modules/skottie",
|
||||||
|
"modules/skparagraph",
|
||||||
"modules/skshaper",
|
"modules/skshaper",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- `CanvasKit.MakeAnimatedImageFromEncoded`, `SkCanvas.drawAnimatedImage`.
|
- `CanvasKit.MakeAnimatedImageFromEncoded`, `SkCanvas.drawAnimatedImage`.
|
||||||
- `CanvasKit.SkFontMgr.FromData` which takes several ArrayBuffers of font data, parses
|
- `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.
|
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
|
### Changed
|
||||||
- The `no_font` compile option should strip out more dead code related to fonts.
|
- The `no_font` compile option should strip out more dead code related to fonts.
|
||||||
|
@ -19,5 +19,6 @@ using JSObject = emscripten::val;
|
|||||||
using JSString = emscripten::val;
|
using JSString = emscripten::val;
|
||||||
using SkPathOrNull = emscripten::val;
|
using SkPathOrNull = emscripten::val;
|
||||||
using Uint8Array = emscripten::val;
|
using Uint8Array = emscripten::val;
|
||||||
|
using Float32Array = emscripten::val;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -26,6 +26,9 @@
|
|||||||
<h2> Particles </h2>
|
<h2> Particles </h2>
|
||||||
<canvas id=particles width=500 height=500></canvas>
|
<canvas id=particles width=500 height=500></canvas>
|
||||||
|
|
||||||
|
<h2> Paragraph </h2>
|
||||||
|
<canvas id=para1 width=500 height=500></canvas>
|
||||||
|
|
||||||
<script type="text/javascript" src="/node_modules/canvaskit/bin/canvaskit.js"></script>
|
<script type="text/javascript" src="/node_modules/canvaskit/bin/canvaskit.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" charset="utf-8">
|
<script type="text/javascript" charset="utf-8">
|
||||||
@ -63,6 +66,8 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
ParticlesAPI1(CanvasKit);
|
ParticlesAPI1(CanvasKit);
|
||||||
|
|
||||||
|
ParagraphAPI1(CanvasKit, robotoData);
|
||||||
});
|
});
|
||||||
|
|
||||||
fetch('https://storage.googleapis.com/skia-cdn/misc/lego_loader.json').then((resp) => {
|
fetch('https://storage.googleapis.com/skia-cdn/misc/lego_loader.json').then((resp) => {
|
||||||
@ -127,6 +132,7 @@
|
|||||||
SkottieExample(CanvasKit, 'sk_webfont', webfontJSON, fullBounds, {
|
SkottieExample(CanvasKit, 'sk_webfont', webfontJSON, fullBounds, {
|
||||||
'Roboto-Regular': robotoData,
|
'Roboto-Regular': robotoData,
|
||||||
});
|
});
|
||||||
|
ParagraphAPI1(CanvasKit, robotoData);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -312,4 +318,66 @@ const snowfall = {
|
|||||||
window.requestAnimationFrame(drawFrame);
|
window.requestAnimationFrame(drawFrame);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.SkFontMgr.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 font = new CanvasKit.SkFont(null, 18);
|
||||||
|
const fontPaint = new CanvasKit.SkPaint();
|
||||||
|
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);
|
||||||
|
|
||||||
|
let posA = paragraph.getGlyphPositionAtCoordinate(X, Y);
|
||||||
|
canvas.drawText(`At (${X}, ${Y}) glyph is ${posA.pos}`, 5, 450, fontPaint, font);
|
||||||
|
|
||||||
|
surface.requestAnimationFrame(drawFrame);
|
||||||
|
}
|
||||||
|
surface.requestAnimationFrame(drawFrame);
|
||||||
|
|
||||||
|
let interact = (e) => {
|
||||||
|
X = e.offsetX;
|
||||||
|
Y = e.offsetY;
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById('para1').addEventListener('pointermove', interact);
|
||||||
|
return surface;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -68,6 +68,9 @@
|
|||||||
#include <emscripten/html5.h>
|
#include <emscripten/html5.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef SK_INCLUDE_PARAGRAPH
|
||||||
|
#include "modules/skparagraph/include/Paragraph.h"
|
||||||
|
#endif
|
||||||
// Aliases for less typing
|
// Aliases for less typing
|
||||||
using BoneIndices = SkVertices::BoneIndices;
|
using BoneIndices = SkVertices::BoneIndices;
|
||||||
using BoneWeights = SkVertices::BoneWeights;
|
using BoneWeights = SkVertices::BoneWeights;
|
||||||
@ -846,6 +849,12 @@ EMSCRIPTEN_BINDINGS(Skia) {
|
|||||||
.function("drawLine", select_overload<void (SkScalar, SkScalar, SkScalar, SkScalar, const SkPaint&)>(&SkCanvas::drawLine))
|
.function("drawLine", select_overload<void (SkScalar, SkScalar, SkScalar, SkScalar, const SkPaint&)>(&SkCanvas::drawLine))
|
||||||
.function("drawOval", &SkCanvas::drawOval)
|
.function("drawOval", &SkCanvas::drawOval)
|
||||||
.function("drawPaint", &SkCanvas::drawPaint)
|
.function("drawPaint", &SkCanvas::drawPaint)
|
||||||
|
#ifdef SK_INCLUDE_PARAGRAPH
|
||||||
|
.function("drawParagraph", optional_override([](SkCanvas& self, skia::textlayout::Paragraph* p,
|
||||||
|
SkScalar x, SkScalar y) {
|
||||||
|
p->paint(&self, x, y);
|
||||||
|
}), allow_raw_pointers())
|
||||||
|
#endif
|
||||||
.function("drawPath", &SkCanvas::drawPath)
|
.function("drawPath", &SkCanvas::drawPath)
|
||||||
// Of note, picture is *not* what is colloquially thought of as a "picture", what we call
|
// Of note, picture is *not* what is colloquially thought of as a "picture", what we call
|
||||||
// a bitmap. An SkPicture is a series of draw commands.
|
// a bitmap. An SkPicture is a series of draw commands.
|
||||||
@ -909,8 +918,7 @@ EMSCRIPTEN_BINDINGS(Skia) {
|
|||||||
SkImageInfo dstInfo = toSkImageInfo(di);
|
SkImageInfo dstInfo = toSkImageInfo(di);
|
||||||
|
|
||||||
return self.writePixels(dstInfo, pixels, srcRowBytes, dstX, dstY);
|
return self.writePixels(dstInfo, pixels, srcRowBytes, dstX, dstY);
|
||||||
}))
|
}));
|
||||||
;
|
|
||||||
|
|
||||||
class_<SkColorFilter>("SkColorFilter")
|
class_<SkColorFilter>("SkColorFilter")
|
||||||
.smart_ptr<sk_sp<SkColorFilter>>("sk_sp<SkColorFilter>>")
|
.smart_ptr<sk_sp<SkColorFilter>>("sk_sp<SkColorFilter>>")
|
||||||
|
@ -133,6 +133,11 @@ if [[ $@ == *primitive_shaper* ]]; then
|
|||||||
SHAPER_TARGETS=""
|
SHAPER_TARGETS=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
PARAGRAPH_JS="--pre-js $BASE_DIR/paragraph.js"
|
||||||
|
PARAGRAPH_LIB="$BUILD_DIR/libskparagraph.a"
|
||||||
|
PARAGRAPH_BINDINGS="-DSK_INCLUDE_PARAGRAPH=1 \
|
||||||
|
$BASE_DIR/paragraph_bindings.cpp"
|
||||||
|
|
||||||
# Turn off exiting while we check for ninja (which may not be on PATH)
|
# Turn off exiting while we check for ninja (which may not be on PATH)
|
||||||
set +e
|
set +e
|
||||||
NINJA=`which ninja`
|
NINJA=`which ninja`
|
||||||
@ -180,7 +185,7 @@ echo "Compiling bitcode"
|
|||||||
skia_use_piex=false \
|
skia_use_piex=false \
|
||||||
skia_use_system_libpng=true \
|
skia_use_system_libpng=true \
|
||||||
skia_use_system_freetype2=true \
|
skia_use_system_freetype2=true \
|
||||||
skia_use_system_libjpeg_turbo = false \
|
skia_use_system_libjpeg_turbo=false \
|
||||||
skia_use_vulkan=false \
|
skia_use_vulkan=false \
|
||||||
skia_use_wuffs = true \
|
skia_use_wuffs = true \
|
||||||
skia_use_zlib=true \
|
skia_use_zlib=true \
|
||||||
@ -193,10 +198,12 @@ echo "Compiling bitcode"
|
|||||||
skia_enable_skshaper=true \
|
skia_enable_skshaper=true \
|
||||||
skia_enable_ccpr=false \
|
skia_enable_ccpr=false \
|
||||||
skia_enable_nvpr=false \
|
skia_enable_nvpr=false \
|
||||||
|
skia_enable_skparagraph=true \
|
||||||
skia_enable_pdf=false"
|
skia_enable_pdf=false"
|
||||||
|
|
||||||
# Build all the libs, we'll link the appropriate ones down below
|
# Build all the libs, we'll link the appropriate ones down below
|
||||||
${NINJA} -C ${BUILD_DIR} libskia.a libskottie.a libsksg.a libskshaper.a libparticles.a $SHAPER_TARGETS
|
${NINJA} -C ${BUILD_DIR} libskia.a libskottie.a libsksg.a \
|
||||||
|
libskparagraph.a libskshaper.a libparticles.a $SHAPER_TARGETS
|
||||||
|
|
||||||
export EMCC_CLOSURE_ARGS="--externs $BASE_DIR/externs.js "
|
export EMCC_CLOSURE_ARGS="--externs $BASE_DIR/externs.js "
|
||||||
|
|
||||||
@ -210,6 +217,7 @@ ${EMCXX} \
|
|||||||
-I. \
|
-I. \
|
||||||
-Ithird_party/icu \
|
-Ithird_party/icu \
|
||||||
-Ithird_party/skcms \
|
-Ithird_party/skcms \
|
||||||
|
-Ithird_party/externals/icu/source/common/ \
|
||||||
-DSK_DISABLE_READBUFFER \
|
-DSK_DISABLE_READBUFFER \
|
||||||
-DSK_DISABLE_AAA \
|
-DSK_DISABLE_AAA \
|
||||||
$WASM_GPU \
|
$WASM_GPU \
|
||||||
@ -219,6 +227,7 @@ ${EMCXX} \
|
|||||||
--pre-js $BASE_DIR/preamble.js \
|
--pre-js $BASE_DIR/preamble.js \
|
||||||
--pre-js $BASE_DIR/helper.js \
|
--pre-js $BASE_DIR/helper.js \
|
||||||
--pre-js $BASE_DIR/interface.js \
|
--pre-js $BASE_DIR/interface.js \
|
||||||
|
$PARAGRAPH_JS \
|
||||||
$SKOTTIE_JS \
|
$SKOTTIE_JS \
|
||||||
$HTML_CANVAS_API \
|
$HTML_CANVAS_API \
|
||||||
--pre-js $BASE_DIR/postamble.js \
|
--pre-js $BASE_DIR/postamble.js \
|
||||||
@ -228,8 +237,10 @@ ${EMCXX} \
|
|||||||
$PARTICLES_BINDINGS \
|
$PARTICLES_BINDINGS \
|
||||||
$SKOTTIE_BINDINGS \
|
$SKOTTIE_BINDINGS \
|
||||||
$MANAGED_SKOTTIE_BINDINGS \
|
$MANAGED_SKOTTIE_BINDINGS \
|
||||||
|
$PARAGRAPH_BINDINGS \
|
||||||
$SKOTTIE_LIB \
|
$SKOTTIE_LIB \
|
||||||
$PARTICLES_LIB \
|
$PARTICLES_LIB \
|
||||||
|
$PARAGRAPH_LIB \
|
||||||
$BUILD_DIR/libskshaper.a \
|
$BUILD_DIR/libskshaper.a \
|
||||||
$SHAPER_LIB \
|
$SHAPER_LIB \
|
||||||
$BUILD_DIR/libskia.a \
|
$BUILD_DIR/libskia.a \
|
||||||
|
@ -96,6 +96,18 @@ var CanvasKit = {
|
|||||||
setResourceCacheLimitBytes: function() {},
|
setResourceCacheLimitBytes: function() {},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Paragraph: {
|
||||||
|
// public API (from C++ bindings)
|
||||||
|
getGlyphPositionAtCoordinate: function() {},
|
||||||
|
layout: function() {},
|
||||||
|
|
||||||
|
// private API
|
||||||
|
/** @return {Float32Array} */
|
||||||
|
_getRectsForRange: function() {},
|
||||||
|
},
|
||||||
|
|
||||||
|
ParagraphStyle: function() {},
|
||||||
|
|
||||||
RSXFormBuilder: function() {},
|
RSXFormBuilder: function() {},
|
||||||
SkColorBuilder: function() {},
|
SkColorBuilder: function() {},
|
||||||
SkRectBuilder: function() {},
|
SkRectBuilder: function() {},
|
||||||
@ -126,6 +138,7 @@ var CanvasKit = {
|
|||||||
drawLine: function() {},
|
drawLine: function() {},
|
||||||
drawOval: function() {},
|
drawOval: function() {},
|
||||||
drawPaint: function() {},
|
drawPaint: function() {},
|
||||||
|
drawParagraph: function() {},
|
||||||
drawPath: function() {},
|
drawPath: function() {},
|
||||||
drawPicture: function() {},
|
drawPicture: function() {},
|
||||||
drawRRect: function() {},
|
drawRRect: function() {},
|
||||||
@ -379,6 +392,8 @@ var CanvasKit = {
|
|||||||
texCoords: function() {},
|
texCoords: function() {},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
TextStyle: function() {},
|
||||||
|
|
||||||
// Constants and Enums
|
// Constants and Enums
|
||||||
gpu: {},
|
gpu: {},
|
||||||
skottie: {},
|
skottie: {},
|
||||||
@ -398,6 +413,16 @@ var CanvasKit = {
|
|||||||
CUBIC_VERB: {},
|
CUBIC_VERB: {},
|
||||||
CLOSE_VERB: {},
|
CLOSE_VERB: {},
|
||||||
|
|
||||||
|
NoDecoration: {},
|
||||||
|
UnderlineDecoration: {},
|
||||||
|
OverlineDecoration: {},
|
||||||
|
LineThroughDecoration: {},
|
||||||
|
|
||||||
|
Affinity: {
|
||||||
|
Upstream: {},
|
||||||
|
Downstream: {},
|
||||||
|
},
|
||||||
|
|
||||||
AlphaType: {
|
AlphaType: {
|
||||||
Opaque: {},
|
Opaque: {},
|
||||||
Premul: {},
|
Premul: {},
|
||||||
@ -495,6 +520,16 @@ var CanvasKit = {
|
|||||||
ReverseDifference: {},
|
ReverseDifference: {},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
RectHeightStyle: {
|
||||||
|
Tight: {},
|
||||||
|
Max: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
RectWidthStyle: {
|
||||||
|
Tight: {},
|
||||||
|
Max: {},
|
||||||
|
},
|
||||||
|
|
||||||
StrokeCap: {
|
StrokeCap: {
|
||||||
Butt: {},
|
Butt: {},
|
||||||
Round: {},
|
Round: {},
|
||||||
@ -507,6 +542,15 @@ var CanvasKit = {
|
|||||||
Bevel: {},
|
Bevel: {},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
TextAlign: {
|
||||||
|
Left: {},
|
||||||
|
Right: {},
|
||||||
|
Center: {},
|
||||||
|
Justify: {},
|
||||||
|
Start: {},
|
||||||
|
End: {},
|
||||||
|
},
|
||||||
|
|
||||||
TextEncoding: {
|
TextEncoding: {
|
||||||
UTF8: {},
|
UTF8: {},
|
||||||
UTF16: {},
|
UTF16: {},
|
||||||
@ -561,6 +605,8 @@ var CanvasKit = {
|
|||||||
// Public API things that are newly declared in the JS should go here.
|
// Public API things that are newly declared in the JS should go here.
|
||||||
// It's not enough to declare them above, because closure can still erase them
|
// It's not enough to declare them above, because closure can still erase them
|
||||||
// unless they go on the prototype.
|
// unless they go on the prototype.
|
||||||
|
CanvasKit.Paragraph.prototype.getRectsForRange = function() {};
|
||||||
|
|
||||||
CanvasKit.SkPath.prototype.addArc = function() {};
|
CanvasKit.SkPath.prototype.addArc = function() {};
|
||||||
CanvasKit.SkPath.prototype.addOval = function() {};
|
CanvasKit.SkPath.prototype.addOval = function() {};
|
||||||
CanvasKit.SkPath.prototype.addPath = function() {};
|
CanvasKit.SkPath.prototype.addPath = function() {};
|
||||||
|
@ -704,14 +704,14 @@ CanvasKit.onRuntimeInitialized = function() {
|
|||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
// arguments should all be an arrayBuffer or be an array of arrayBuffers.
|
// arguments should all be arrayBuffers or be an array of arrayBuffers.
|
||||||
CanvasKit.SkFontMgr.FromData = function() {
|
CanvasKit.SkFontMgr.FromData = function() {
|
||||||
if (!arguments.length) {
|
if (!arguments.length) {
|
||||||
SkDebug('Could not make SkFontMgr from no font sources');
|
SkDebug('Could not make SkFontMgr from no font sources');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
var fonts = arguments;
|
var fonts = arguments;
|
||||||
if (Array.isArray(fonts) && fonts.length === 1) {
|
if (fonts.length === 1 && Array.isArray(fonts[0])) {
|
||||||
fonts = arguments[0];
|
fonts = arguments[0];
|
||||||
}
|
}
|
||||||
if (!fonts.length) {
|
if (!fonts.length) {
|
||||||
|
76
modules/canvaskit/paragraph.js
Normal file
76
modules/canvaskit/paragraph.js
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
(function(CanvasKit){
|
||||||
|
CanvasKit._extraInitializations = CanvasKit._extraInitializations || [];
|
||||||
|
CanvasKit._extraInitializations.push(function() {
|
||||||
|
|
||||||
|
CanvasKit.Paragraph.prototype.getRectsForRange = function(start, end, hStyle, wStyle) {
|
||||||
|
/**
|
||||||
|
* This is bytes, but we'll want to think about them as float32s
|
||||||
|
* @type {Float32Array}
|
||||||
|
*/
|
||||||
|
var floatArray = this._getRectsForRange(start, end, hStyle, wStyle);
|
||||||
|
|
||||||
|
if (!floatArray || !floatArray.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
var ret = [];
|
||||||
|
for (var i = 0; i < floatArray.length; i+=4) {
|
||||||
|
ret.push(CanvasKit.LTRBRect(floatArray[i], floatArray[i+1], floatArray[i+2], floatArray[i+3]))
|
||||||
|
}
|
||||||
|
CanvasKit._free(floatArray.byteOffset);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// These helpers fill out all fields, because emscripten complains if we
|
||||||
|
// have undefined and it expects, for example, a float.
|
||||||
|
CanvasKit.ParagraphStyle = function(s) {
|
||||||
|
// Use [''] to tell closure not to minify the names
|
||||||
|
s['heightMultiplier'] = s['heightMultiplier'] || 0;
|
||||||
|
s['maxLines'] = s['maxLines'] || 0;
|
||||||
|
s['textAlign'] = s['textAlign'] || CanvasKit.TextAlign.Start;
|
||||||
|
s['textStyle'] = CanvasKit.TextStyle(s['textStyle']);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
CanvasKit.TextStyle = function(s) {
|
||||||
|
// Use [''] to tell closure not to minify the names
|
||||||
|
s['backgroundColor'] = s['backgroundColor'] || 0;
|
||||||
|
s['color'] = s['color'] || 0;
|
||||||
|
s['decoration'] = s['decoration'] || 0;
|
||||||
|
s['decorationThickness'] = s['decorationThickness'] || 0;
|
||||||
|
s['fontSize'] = s['fontSize'] || 0;
|
||||||
|
if (Array.isArray(s['fontFamilies']) && s['fontFamilies'].length) {
|
||||||
|
var sPtr = naiveCopyStrArray(s['fontFamilies']);
|
||||||
|
s['_fontFamilies'] = sPtr;
|
||||||
|
s['_numFontFamilies'] = s['fontFamilies'].length;
|
||||||
|
} else {
|
||||||
|
s['_fontFamilies'] = nullptr;
|
||||||
|
s['_numFontFamilies'] = 0;
|
||||||
|
}
|
||||||
|
s['foregroundColor'] = s['foregroundColor'] || 0;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns a pointer to a place on the heap that has an array
|
||||||
|
// of char* (effectively a char**). For now, this does the naive thing
|
||||||
|
// and depends on the string being null-terminated. This should be used
|
||||||
|
// for simple, well-formed things (e.g. font-families), not arbitrary
|
||||||
|
// text that should be drawn. If we need this to handle more complex
|
||||||
|
// strings, it should return two pointers, a pointer of the
|
||||||
|
// string array and a pointer to an array of the strings byte lengths.
|
||||||
|
function naiveCopyStrArray(strings) {
|
||||||
|
if (!strings || !strings.length) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
var sPtrs = [];
|
||||||
|
for (var i = 0; i < strings.length; i++) {
|
||||||
|
var str = strings[i];
|
||||||
|
// Add 1 for null terminator, which we need when copying/converting
|
||||||
|
var strLen = lengthBytesUTF8(str) + 1;
|
||||||
|
var strPtr = CanvasKit._malloc(strLen);
|
||||||
|
stringToUTF8(str, strPtr, strLen);
|
||||||
|
sPtrs.push(strPtr);
|
||||||
|
}
|
||||||
|
return copy1dArray(sPtrs, CanvasKit.HEAPU32);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}(Module)); // When this file is loaded in, the high level object is "Module";
|
196
modules/canvaskit/paragraph_bindings.cpp
Normal file
196
modules/canvaskit/paragraph_bindings.cpp
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Google LLC
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by a BSD-style license that can be
|
||||||
|
* found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "include/core/SkColor.h"
|
||||||
|
#include "include/core/SkString.h"
|
||||||
|
|
||||||
|
#include "modules/skparagraph/include/DartTypes.h"
|
||||||
|
#include "modules/skparagraph/include/Paragraph.h"
|
||||||
|
#include "modules/skparagraph/include/ParagraphBuilder.h"
|
||||||
|
#include "modules/skparagraph/include/TextStyle.h"
|
||||||
|
#include "modules/skparagraph/src/ParagraphBuilderImpl.h"
|
||||||
|
#include "modules/skparagraph/src/ParagraphImpl.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <emscripten.h>
|
||||||
|
#include <emscripten/bind.h>
|
||||||
|
#include "modules/canvaskit/WasmAliases.h"
|
||||||
|
|
||||||
|
using namespace emscripten;
|
||||||
|
|
||||||
|
namespace para = skia::textlayout;
|
||||||
|
|
||||||
|
struct SimpleTextStyle {
|
||||||
|
SkColor color;
|
||||||
|
SkColor foregroundColor;
|
||||||
|
SkColor backgroundColor;
|
||||||
|
uint8_t decoration;
|
||||||
|
SkScalar fontSize;
|
||||||
|
SkScalar decorationThickness;
|
||||||
|
uintptr_t /* const char** */ fontFamilies;
|
||||||
|
int numFontFamilies;
|
||||||
|
};
|
||||||
|
|
||||||
|
para::TextStyle toTextStyle(const SimpleTextStyle& s) {
|
||||||
|
para::TextStyle ts;
|
||||||
|
if (s.color != 0) {
|
||||||
|
ts.setColor(s.color);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.foregroundColor != 0) {
|
||||||
|
SkPaint p;
|
||||||
|
p.setColor(s.foregroundColor);
|
||||||
|
ts.setForegroundColor(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.backgroundColor != 0) {
|
||||||
|
SkPaint p;
|
||||||
|
p.setColor(s.backgroundColor);
|
||||||
|
ts.setBackgroundColor(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.fontSize != 0) {
|
||||||
|
ts.setFontSize(s.fontSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
ts.setDecoration(para::TextDecoration(s.decoration));
|
||||||
|
if (s.decorationThickness != 0) {
|
||||||
|
ts.setDecorationThicknessMultiplier(s.decorationThickness);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char** fontFamilies = reinterpret_cast<const char**>(s.fontFamilies);
|
||||||
|
if (s.numFontFamilies > 0 && fontFamilies != nullptr) {
|
||||||
|
std::vector<SkString> ff;
|
||||||
|
for (int i = 0; i< s.numFontFamilies; i++) {
|
||||||
|
ff.emplace_back(fontFamilies[i]);
|
||||||
|
}
|
||||||
|
ts.setFontFamilies(ff);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SimpleParagraphStyle {
|
||||||
|
SimpleTextStyle textStyle;
|
||||||
|
SkScalar heightMultiplier;
|
||||||
|
para::TextAlign textAlign;
|
||||||
|
size_t maxLines;
|
||||||
|
};
|
||||||
|
|
||||||
|
para::ParagraphStyle toParagraphStyle(const SimpleParagraphStyle& s) {
|
||||||
|
para::ParagraphStyle ps;
|
||||||
|
auto ts = toTextStyle(s.textStyle);
|
||||||
|
ps.setTextStyle(ts);
|
||||||
|
if (s.heightMultiplier != 0) {
|
||||||
|
ps.setHeight(s.heightMultiplier);
|
||||||
|
}
|
||||||
|
ps.setTextAlign(s.textAlign);
|
||||||
|
if (s.maxLines != 0) {
|
||||||
|
ps.setMaxLines(s.maxLines);
|
||||||
|
}
|
||||||
|
return ps;
|
||||||
|
}
|
||||||
|
|
||||||
|
Float32Array GetRectsForRange(para::ParagraphImpl& self, unsigned start, unsigned end,
|
||||||
|
para::RectHeightStyle heightStyle, para::RectWidthStyle widthStyle) {
|
||||||
|
std::vector<para::TextBox> boxes = self.getRectsForRange(start, end, heightStyle, widthStyle);
|
||||||
|
// Pack these text boxes into an array of n groups of 4 SkScalar (floats)
|
||||||
|
if (!boxes.size()) {
|
||||||
|
return emscripten::val::null();
|
||||||
|
}
|
||||||
|
SkRect* rects = new SkRect[boxes.size()];
|
||||||
|
for (int i = 0; i< boxes.size(); i++) {
|
||||||
|
rects[i] = boxes[i].rect;
|
||||||
|
}
|
||||||
|
float* fPtr = reinterpret_cast<float*>(rects);
|
||||||
|
// Of note: now that we have cast rects to float*, emscripten is smart enough to wrap this
|
||||||
|
// into a Float32Array for us.
|
||||||
|
return Float32Array(typed_memory_view(boxes.size()*4, fPtr));
|
||||||
|
}
|
||||||
|
|
||||||
|
EMSCRIPTEN_BINDINGS(Paragraph) {
|
||||||
|
|
||||||
|
class_<para::Paragraph>("Paragraph");
|
||||||
|
|
||||||
|
// This "base<>" tells Emscripten that ParagraphImpl is a Paragraph and can get substituted
|
||||||
|
// in properly in drawParagraph. However, Emscripten will not let us bind pure virtual methods
|
||||||
|
// so we have to "expose" the ParagraphImpl and its methods.
|
||||||
|
class_<para::ParagraphImpl, base<para::Paragraph>>("ParagraphImpl")
|
||||||
|
.function("_getRectsForRange", &GetRectsForRange)
|
||||||
|
.function("getGlyphPositionAtCoordinate", ¶::ParagraphImpl::getGlyphPositionAtCoordinate)
|
||||||
|
.function("layout", ¶::ParagraphImpl::layout);
|
||||||
|
|
||||||
|
class_<para::ParagraphBuilderImpl>("ParagraphBuilder")
|
||||||
|
.class_function("Make", optional_override([](SimpleParagraphStyle style,
|
||||||
|
sk_sp<SkFontMgr> fontMgr)-> para::ParagraphBuilderImpl {
|
||||||
|
auto fc = sk_make_sp<para::FontCollection>();
|
||||||
|
fc->setDefaultFontManager(fontMgr);
|
||||||
|
auto ps = toParagraphStyle(style);
|
||||||
|
para::ParagraphBuilderImpl pbi(ps, fc);
|
||||||
|
return pbi;
|
||||||
|
}), allow_raw_pointers())
|
||||||
|
.function("addText", optional_override([](para::ParagraphBuilderImpl& self, std::string text) {
|
||||||
|
return self.addText(text.c_str(), text.length());
|
||||||
|
}))
|
||||||
|
.function("build", ¶::ParagraphBuilderImpl::Build, allow_raw_pointers())
|
||||||
|
.function("pop", ¶::ParagraphBuilderImpl::pop)
|
||||||
|
.function("pushStyle", optional_override([](para::ParagraphBuilderImpl& self,
|
||||||
|
SimpleTextStyle textStyle) {
|
||||||
|
auto ts = toTextStyle(textStyle);
|
||||||
|
self.pushStyle(ts);
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
enum_<para::Affinity>("Affinity")
|
||||||
|
.value("Upstream", para::Affinity::kUpstream)
|
||||||
|
.value("Downstream", para::Affinity::kDownstream);
|
||||||
|
|
||||||
|
enum_<para::RectHeightStyle>("RectHeightStyle")
|
||||||
|
.value("Tight", para::RectHeightStyle::kTight)
|
||||||
|
.value("Max", para::RectHeightStyle::kMax);
|
||||||
|
|
||||||
|
enum_<para::RectWidthStyle>("RectWidthStyle")
|
||||||
|
.value("Tight", para::RectWidthStyle::kTight)
|
||||||
|
.value("Max", para::RectWidthStyle::kMax);
|
||||||
|
|
||||||
|
enum_<para::TextAlign>("TextAlign")
|
||||||
|
.value("Left", para::TextAlign::kLeft)
|
||||||
|
.value("Right", para::TextAlign::kRight)
|
||||||
|
.value("Center", para::TextAlign::kCenter)
|
||||||
|
.value("Justify", para::TextAlign::kJustify)
|
||||||
|
.value("Start", para::TextAlign::kStart)
|
||||||
|
.value("End", para::TextAlign::kEnd);
|
||||||
|
|
||||||
|
|
||||||
|
value_object<para::PositionWithAffinity>("PositionWithAffinity")
|
||||||
|
.field("pos", ¶::PositionWithAffinity::position)
|
||||||
|
.field("affinity", ¶::PositionWithAffinity::affinity);
|
||||||
|
|
||||||
|
value_object<SimpleParagraphStyle>("ParagraphStyle")
|
||||||
|
.field("heightMultiplier", &SimpleParagraphStyle::heightMultiplier)
|
||||||
|
.field("maxLines", &SimpleParagraphStyle::maxLines)
|
||||||
|
.field("textAlign", &SimpleParagraphStyle::textAlign)
|
||||||
|
.field("textStyle", &SimpleParagraphStyle::textStyle);
|
||||||
|
|
||||||
|
value_object<SimpleTextStyle>("TextStyle")
|
||||||
|
.field("backgroundColor", &SimpleTextStyle::backgroundColor)
|
||||||
|
.field("color", &SimpleTextStyle::color)
|
||||||
|
.field("decoration", &SimpleTextStyle::decoration)
|
||||||
|
.field("decorationThickness", &SimpleTextStyle::decorationThickness)
|
||||||
|
.field("_fontFamilies", &SimpleTextStyle::fontFamilies)
|
||||||
|
.field("fontSize", &SimpleTextStyle::fontSize)
|
||||||
|
.field("foregroundColor", &SimpleTextStyle::foregroundColor)
|
||||||
|
.field("_numFontFamilies", &SimpleTextStyle::numFontFamilies);
|
||||||
|
|
||||||
|
// TextDecoration should be a const because they can be combined
|
||||||
|
constant("NoDecoration", int(para::TextDecoration::kNoDecoration));
|
||||||
|
constant("UnderlineDecoration", int(para::TextDecoration::kUnderline));
|
||||||
|
constant("OverlineDecoration", int(para::TextDecoration::kOverline));
|
||||||
|
constant("LineThroughDecoration", int(para::TextDecoration::kLineThrough));
|
||||||
|
}
|
BIN
modules/canvaskit/tests/assets/NotoColorEmoji.ttf
Normal file
BIN
modules/canvaskit/tests/assets/NotoColorEmoji.ttf
Normal file
Binary file not shown.
297
modules/canvaskit/tests/paragraph.spec.js
Normal file
297
modules/canvaskit/tests/paragraph.spec.js
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
describe('CanvasKit\'s Path Behavior', function() {
|
||||||
|
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 = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
let notoSerifFontBuffer = null;
|
||||||
|
// This font is known to support kerning
|
||||||
|
const notoSerifFontLoaded = fetch('/assets/NotoSerif-Regular.ttf').then(
|
||||||
|
(response) => response.arrayBuffer()).then(
|
||||||
|
(buffer) => {
|
||||||
|
notoSerifFontBuffer = buffer;
|
||||||
|
});
|
||||||
|
|
||||||
|
let emojiFontBuffer = null;
|
||||||
|
const emojiFontLoaded = fetch('/assets/NotoColorEmoji.ttf').then(
|
||||||
|
(response) => response.arrayBuffer()).then(
|
||||||
|
(buffer) => {
|
||||||
|
emojiFontBuffer = buffer;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('draws shaped text in a paragraph', function(done) {
|
||||||
|
Promise.all([LoadCanvasKit, notoSerifFontLoaded]).then(catchException(done, () => {
|
||||||
|
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.RED);
|
||||||
|
paint.setStyle(CanvasKit.PaintStyle.Stroke);
|
||||||
|
|
||||||
|
const fontMgr = CanvasKit.SkFontMgr.FromData(notoSerifFontBuffer);
|
||||||
|
|
||||||
|
const wrapTo = 200;
|
||||||
|
|
||||||
|
const paraStyle = new CanvasKit.ParagraphStyle({
|
||||||
|
textStyle: {
|
||||||
|
color: CanvasKit.BLACK,
|
||||||
|
fontFamilies: ['Noto Serif'],
|
||||||
|
fontSize: 20,
|
||||||
|
},
|
||||||
|
textAlign: CanvasKit.TextAlign.Center,
|
||||||
|
maxLines: 8,
|
||||||
|
});
|
||||||
|
|
||||||
|
const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
|
||||||
|
builder.addText('VAVAVAVAVAVAVA\nVAVA\n');
|
||||||
|
|
||||||
|
const blueText = new CanvasKit.TextStyle({
|
||||||
|
backgroundColor: CanvasKit.Color(234, 208, 232), // light pink
|
||||||
|
color: CanvasKit.Color(48, 37, 199),
|
||||||
|
decoration: CanvasKit.LineThroughDecoration,
|
||||||
|
decorationThickness: 1.5, // multiplier based on font size
|
||||||
|
fontSize: 24,
|
||||||
|
});
|
||||||
|
builder.pushStyle(blueText)
|
||||||
|
builder.addText(`Gosh I hope this wraps at some point, it is such a long line.`)
|
||||||
|
builder.pop();
|
||||||
|
builder.addText(` I'm done with the blue now. `)
|
||||||
|
builder.addText(`Now I hope we should stop before we get 8 lines tall. `);
|
||||||
|
const paragraph = builder.build();
|
||||||
|
|
||||||
|
paragraph.layout(wrapTo);
|
||||||
|
|
||||||
|
canvas.drawRect(CanvasKit.LTRBRect(10, 10, wrapTo+10, wrapTo+10), paint);
|
||||||
|
canvas.drawParagraph(paragraph, 10, 10);
|
||||||
|
|
||||||
|
surface.flush();
|
||||||
|
|
||||||
|
paint.delete();
|
||||||
|
fontMgr.delete();
|
||||||
|
reportSurface(surface, 'paragraph_basic', done);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('provides rectangles around glyph ranges', function(done) {
|
||||||
|
// loosely based on SkParagraph_GetRectsForRangeParagraph test in c++ code.
|
||||||
|
Promise.all([LoadCanvasKit, notoSerifFontLoaded]).then(catchException(done, () => {
|
||||||
|
const surface = CanvasKit.MakeCanvasSurface('test');
|
||||||
|
expect(surface).toBeTruthy('Could not make surface')
|
||||||
|
if (!surface) {
|
||||||
|
done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const canvas = surface.getCanvas();
|
||||||
|
|
||||||
|
const fontMgr = CanvasKit.SkFontMgr.FromData(notoSerifFontBuffer);
|
||||||
|
|
||||||
|
const wrapTo = 550;
|
||||||
|
const hStyle = CanvasKit.RectHeightStyle.Max;
|
||||||
|
const wStyle = CanvasKit.RectWidthStyle.Tight;
|
||||||
|
|
||||||
|
const paraStyle = new CanvasKit.ParagraphStyle({
|
||||||
|
textStyle: {
|
||||||
|
color: CanvasKit.BLACK,
|
||||||
|
fontFamilies: ['Noto Serif'],
|
||||||
|
fontSize: 50,
|
||||||
|
// TODO(kjlubick): font style
|
||||||
|
},
|
||||||
|
textAlign: CanvasKit.TextAlign.Left,
|
||||||
|
maxLines: 10,
|
||||||
|
});
|
||||||
|
const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
|
||||||
|
builder.addText('12345, \"67890\" 12345 67890 12345 67890 12345 67890 12345 67890 12345 67890 12345');
|
||||||
|
const paragraph = builder.build();
|
||||||
|
|
||||||
|
paragraph.layout(wrapTo);
|
||||||
|
|
||||||
|
const ranges = [
|
||||||
|
{
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
expectedNum: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: 0,
|
||||||
|
end: 1,
|
||||||
|
expectedNum: 1,
|
||||||
|
color: CanvasKit.Color(200, 0, 200),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: 2,
|
||||||
|
end: 8,
|
||||||
|
expectedNum: 1,
|
||||||
|
color: CanvasKit.Color(255, 0, 0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: 8,
|
||||||
|
end: 21,
|
||||||
|
expectedNum: 1,
|
||||||
|
color: CanvasKit.Color(0, 255, 0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: 30,
|
||||||
|
end: 100,
|
||||||
|
expectedNum: 4,
|
||||||
|
color: CanvasKit.Color(0, 0, 255),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: 19,
|
||||||
|
end: 22,
|
||||||
|
expectedNum: 1,
|
||||||
|
color: CanvasKit.Color(0, 200, 200),
|
||||||
|
}
|
||||||
|
];
|
||||||
|
// Move it down a bit so we can see the rects that go above 0,0
|
||||||
|
canvas.translate(10, 10);
|
||||||
|
canvas.drawParagraph(paragraph, 0, 0);
|
||||||
|
|
||||||
|
for (const test of ranges) {
|
||||||
|
let rects = paragraph.getRectsForRange(test.start, test.end, hStyle, wStyle);
|
||||||
|
expect(Array.isArray(rects)).toEqual(true);
|
||||||
|
expect(rects.length).toEqual(test.expectedNum);
|
||||||
|
|
||||||
|
for (const rect of rects) {
|
||||||
|
const p = new CanvasKit.SkPaint();
|
||||||
|
p.setColor(test.color);
|
||||||
|
p.setStyle(CanvasKit.PaintStyle.Stroke);
|
||||||
|
canvas.drawRect(rect, p);
|
||||||
|
p.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
surface.flush();
|
||||||
|
fontMgr.delete();
|
||||||
|
reportSurface(surface, 'paragraph_rects', done);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Disabled until we can update CanvasKit's freetype.
|
||||||
|
xit('can draw emojis', function(done) {
|
||||||
|
Promise.all([LoadCanvasKit, notoSerifFontLoaded, emojiFontLoaded]).then(catchException(done, () => {
|
||||||
|
const surface = CanvasKit.MakeCanvasSurface('test');
|
||||||
|
expect(surface).toBeTruthy('Could not make surface')
|
||||||
|
if (!surface) {
|
||||||
|
done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const canvas = surface.getCanvas();
|
||||||
|
|
||||||
|
const fontMgr = CanvasKit.SkFontMgr.FromData([notoSerifFontBuffer, emojiFontBuffer]);
|
||||||
|
fontMgr.dumpFamilies();
|
||||||
|
|
||||||
|
const wrapTo = 450;
|
||||||
|
|
||||||
|
const paraStyle = new CanvasKit.ParagraphStyle({
|
||||||
|
textStyle: {
|
||||||
|
color: CanvasKit.BLACK,
|
||||||
|
// Put emoji first, otherwise zero-space-joiner will be matched by serif,
|
||||||
|
// and we don't get families or rainbow flags.
|
||||||
|
fontFamilies: ['Noto Color Emoji', 'Noto Serif'],
|
||||||
|
fontSize: 30,
|
||||||
|
},
|
||||||
|
textAlign: CanvasKit.TextAlign.Left,
|
||||||
|
maxLines: 10,
|
||||||
|
});
|
||||||
|
const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
|
||||||
|
// FIXME(kjlubick): We need one style that doesn't have emoji, otherwise the 4 will
|
||||||
|
// be "emoji 4".
|
||||||
|
builder.addText('4 flags on following line:\n');
|
||||||
|
builder.addText(`🏳️🌈 🇮🇹 🇱🇷 🇺🇸\n`);
|
||||||
|
builder.addText('Rainbow Italy Liberia USA\n\n');
|
||||||
|
builder.addText('Emoji below should wrap:\n');
|
||||||
|
builder.addText(`🍕🍔🍟🥝🍱🕶🎩👩👩👦👩👩👧👧👩👩👦👩👩👧👧👩👩👦👩👩👧👧👩👩👦👩👩👧👧👩👩👦👩👩👧👧👩👩👦👩👩👧👧👩👩👦👩👩👧👧`);
|
||||||
|
const paragraph = builder.build();
|
||||||
|
|
||||||
|
paragraph.layout(wrapTo);
|
||||||
|
|
||||||
|
canvas.drawParagraph(paragraph, 10, 10);
|
||||||
|
|
||||||
|
surface.flush();
|
||||||
|
fontMgr.delete();
|
||||||
|
reportSurface(surface, 'paragraph_emoji', done);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
// A bit wrong, check after jlavrova's change lands.
|
||||||
|
xit('can do hit detection on ascii', function(done) {
|
||||||
|
Promise.all([LoadCanvasKit, notoSerifFontLoaded]).then(catchException(done, () => {
|
||||||
|
const surface = CanvasKit.MakeCanvasSurface('test');
|
||||||
|
expect(surface).toBeTruthy('Could not make surface')
|
||||||
|
if (!surface) {
|
||||||
|
done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const canvas = surface.getCanvas();
|
||||||
|
const fontMgr = CanvasKit.SkFontMgr.FromData([notoSerifFontBuffer]);
|
||||||
|
|
||||||
|
const wrapTo = 300;
|
||||||
|
|
||||||
|
const paraStyle = new CanvasKit.ParagraphStyle({
|
||||||
|
textStyle: {
|
||||||
|
color: CanvasKit.BLACK,
|
||||||
|
fontFamilies: ['Noto Serif'],
|
||||||
|
fontSize: 50,
|
||||||
|
},
|
||||||
|
textAlign: CanvasKit.TextAlign.Left,
|
||||||
|
maxLines: 10,
|
||||||
|
});
|
||||||
|
const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
|
||||||
|
builder.addText('UNCOPYRIGHTABLE');
|
||||||
|
const paragraph = builder.build();
|
||||||
|
|
||||||
|
paragraph.layout(wrapTo);
|
||||||
|
|
||||||
|
canvas.translate(10, 10);
|
||||||
|
canvas.drawParagraph(paragraph, 0, 0);
|
||||||
|
|
||||||
|
const paint = new CanvasKit.SkPaint();
|
||||||
|
|
||||||
|
paint.setColor(CanvasKit.Color(255, 0, 0));
|
||||||
|
paint.setStyle(CanvasKit.PaintStyle.Fill);
|
||||||
|
canvas.drawCircle(20, 30, 3, paint);
|
||||||
|
|
||||||
|
paint.setColor(CanvasKit.Color(0, 0, 255));
|
||||||
|
canvas.drawCircle(80, 90, 3, paint);
|
||||||
|
|
||||||
|
paint.setColor(CanvasKit.Color(0, 255, 0));
|
||||||
|
canvas.drawCircle(280, 2, 3, paint);
|
||||||
|
|
||||||
|
let posU = paragraph.getGlyphPositionAtCoordinate(20, 30);
|
||||||
|
expect(posU).toEqual({
|
||||||
|
pos: 1,
|
||||||
|
affinity: CanvasKit.Affinity.Upstream
|
||||||
|
});
|
||||||
|
let posA = paragraph.getGlyphPositionAtCoordinate(80, 90);
|
||||||
|
expect(posA).toEqual({
|
||||||
|
pos: 11,
|
||||||
|
affinity: CanvasKit.Affinity.Downstream
|
||||||
|
});
|
||||||
|
let posG = paragraph.getGlyphPositionAtCoordinate(280, 2);
|
||||||
|
expect(posG).toEqual({
|
||||||
|
pos: 9,
|
||||||
|
affinity: CanvasKit.Affinity.Upstream
|
||||||
|
});
|
||||||
|
|
||||||
|
surface.flush();
|
||||||
|
fontMgr.delete();
|
||||||
|
reportSurface(surface, 'paragraph_hits', done);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@ -62,6 +62,7 @@ struct PositionWithAffinity {
|
|||||||
int32_t position;
|
int32_t position;
|
||||||
Affinity affinity;
|
Affinity affinity;
|
||||||
|
|
||||||
|
PositionWithAffinity() : position(0), affinity(kDownstream) {}
|
||||||
PositionWithAffinity(int32_t p, Affinity a) : position(p), affinity(a) {}
|
PositionWithAffinity(int32_t p, Affinity a) : position(p), affinity(a) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user