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:
parent
bc8e0d8ba0
commit
bc7c754ce3
@ -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
|
||||
|
@ -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)
|
||||
|
@ -358,6 +358,7 @@ var CanvasKit = {
|
||||
getGlyphBounds: function() {},
|
||||
getGlyphIDs: function() {},
|
||||
getGlyphWidths: function() {},
|
||||
getGlyphIntercepts: function() {},
|
||||
},
|
||||
|
||||
// private API (from C++ bindings)
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
20
modules/canvaskit/npm_build/types/index.d.ts
vendored
20
modules/canvaskit/npm_build/types/index.d.ts
vendored
@ -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.
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user