Expose glyphIntercepts to CK

bug: skia:12000
Change-Id: I40f130b0eab21ef4a53ee88eed8f62c4f3a577c1
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/408899
Commit-Queue: Mike Reed <reed@google.com>
Reviewed-by: Yegor Jbanov <yjbanov@google.com>
This commit is contained in:
Mike Reed 2021-05-14 20:55:58 -04:00
parent bc8e0d8ba0
commit bc7c754ce3
7 changed files with 101 additions and 1 deletions

View File

@ -41,4 +41,29 @@ TypedArray MakeTypedArray(int count, const T src[], const char arrayType[]) {
return jarray;
}
/**
* Reads a JS array, and returns a copy of it in the std::vector.
*
* It is up to the caller to ensure the T and arrayType-string match
* e.g. T == uint16_t, arrayType == "Uint16Array"
*
* Note, this checks the JS type, and if it does not match, it will still attempt
* to return a copy, just by performing a 1-element-at-a-time copy
* via emscripten::vecFromJSArray().
*/
template <typename T>
std::vector<T> CopyTypedArray(JSArray src, const char arrayType[]) {
if (src.instanceof(emscripten::val::global(arrayType))) {
const size_t len = src["length"].as<size_t>();
std::vector<T> dst;
dst.resize(len);
auto dst_view = emscripten::val(typed_memory_view(len, dst.data()));
dst_view.call<void>("set", src);
return dst;
} else {
// copies one at a time
return emscripten::vecFromJSArray<T>(src);
}
}
#endif

View File

@ -1209,6 +1209,19 @@ EMSCRIPTEN_BINDINGS(Skia) {
}
return j;
}))
.function("getGlyphIntercepts", optional_override([](SkFont& self,
JSArray jglyphs,
JSArray jpos,
float top, float bottom) -> JSArray {
auto glyphs = CopyTypedArray<uint16_t>(jglyphs, "Uint16Array");
auto pos = CopyTypedArray<float>(jpos, "Float32Array");
if (glyphs.size() > (pos.size() >> 1)) {
return emscripten::val("Not enough x,y position pairs for glyphs");
}
auto sects = self.getIntercepts(glyphs.data(), SkToInt(glyphs.size()),
(const SkPoint*)pos.data(), top, bottom);
return MakeTypedArray(sects.size(), (const float*)sects.data(), "Float32Array");
}), allow_raw_pointers())
.function("getScaleX", &SkFont::getScaleX)
.function("getSize", &SkFont::getSize)
.function("getSkewX", &SkFont::getSkewX)

View File

@ -358,6 +358,7 @@ var CanvasKit = {
getGlyphBounds: function() {},
getGlyphIDs: function() {},
getGlyphWidths: function() {},
getGlyphIntercepts: function() {},
},
// private API (from C++ bindings)

View File

@ -368,6 +368,7 @@
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;

View File

@ -205,6 +205,7 @@ function MakeStyle(length) {
color: null,
bold: null,
italic: null,
underline: null,
_check_toggle: function(src, dst) {
if (src == 'toggle') {
@ -234,6 +235,9 @@ function MakeStyle(length) {
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;
@ -496,12 +500,32 @@ function MakeEditor(text, style, cursor, width) {
}
LOG(' glyph subrange', glyph_start, glyph_end);
gly = gly.slice(glyph_start, glyph_end);
pos = pos.slice(glyph_start*2, glyph_end*2);
// +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;
}

View File

@ -344,6 +344,22 @@ function fontTests(CK: CanvasKit, face?: Typeface, paint?: Paint) {
font.setSubpixel(true);
font.setTypeface(null);
font.setTypeface(face);
// try unittest for intercepts
{
font.setTypeface(null);
font.setSize(100);
const ids = font.getGlyphIDs('I');
console.assert(ids.length == 1, "");
// aim for the middle of the I at 100 point, expecting a hit
let sects = font.getGlyphIntercepts(ids, [0, 0], -60, -40);
console.assert(sects.length === 2, "expected one pair of intercepts");
// aim below the baseline where we expect no intercepts
sects = font.getGlyphIntercepts(ids, [0, 0], 20, 30);
console.assert(ids.length === 0, "expected no intercepts");
}
}
function fontMgrTests(CK: CanvasKit) {

View File

@ -1659,6 +1659,26 @@ export interface Font extends EmbindObject<Font> {
getGlyphWidths(glyphs: InputGlyphIDArray, paint?: Paint | null,
output?: Float32Array): Float32Array;
/**
* Computes any intersections of a thick "line" and a run of positionsed glyphs.
* The thick line is represented as a top and bottom coordinate (positive for
* below the baseline, negative for above). If there are no intersections
* (e.g. if this is intended as an underline, and there are no "collisions")
* then the returned array will be empty. If there are intersections, the array
* will contain pairs of X coordinates [start, end] for each segment that
* intersected with a glyph.
*
* @param glyphs the glyphs to intersect with
* @param positions x,y coordinates (2 per glyph) for each glyph
* @param top top of the thick "line" to use for intersection testing
* @param bottom bottom of the thick "line" to use for intersection testing
* @param paint optional (can be null) in case the paint affects the
* "thickness" of the glyphs (e.g. patheffect, stroking, maskfilter)
* @return array of [start, end] x-coordinate pairs. Maybe be empty.
*/
getGlyphIntercepts(glyphs: InputGlyphIDArray, positions: Float32Array,
top: number, bottom: number): Float32Array;
/**
* Returns text scale on x-axis. Default value is 1.
*/