Track non-shaping style runs
We only break runs (from the shaper's perspective) on typeface/size changes. All others we just track locally, and then 'chop up' our draws accordingly. Change-Id: I81695772f71175cd5116ed34981ecf16b52123c8 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/405876 Reviewed-by: Mike Reed <reed@google.com>
This commit is contained in:
parent
dabb2891c4
commit
686dd910dd
@ -302,7 +302,8 @@
|
|||||||
const editor = MakeEditor(text0, {typeface:null, size:24}, cursor, 400);
|
const editor = MakeEditor(text0, {typeface:null, size:24}, cursor, 400);
|
||||||
|
|
||||||
editor.applyStyleToRange({size:100}, 0, 1);
|
editor.applyStyleToRange({size:100}, 0, 1);
|
||||||
editor.applyStyleToRange({size:10}, 38, 38+6);
|
editor.applyStyleToRange({italic:true}, 38, 38+6);
|
||||||
|
editor.applyStyleToRange({color:[1,0,0,1]}, 5, 5+4);
|
||||||
|
|
||||||
editor.setXY(LOC_X, LOC_Y);
|
editor.setXY(LOC_X, LOC_Y);
|
||||||
|
|
||||||
@ -342,28 +343,40 @@
|
|||||||
|
|
||||||
function keyhandler(e) {
|
function keyhandler(e) {
|
||||||
switch (e.key) {
|
switch (e.key) {
|
||||||
case 'ArrowLeft': editor.moveDX(-1); break;
|
case 'ArrowLeft': editor.moveDX(-1); return;
|
||||||
case 'ArrowRight': editor.moveDX(1); break;
|
case 'ArrowRight': editor.moveDX(1); return;
|
||||||
case 'ArrowUp':
|
case 'ArrowUp':
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
editor.moveDY(-1);
|
editor.moveDY(-1);
|
||||||
break;
|
return;
|
||||||
case 'ArrowDown':
|
case 'ArrowDown':
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
editor.moveDY(1);
|
editor.moveDY(1);
|
||||||
break;
|
return;
|
||||||
case 'Backspace':
|
case 'Backspace':
|
||||||
editor.deleteSelection();
|
editor.deleteSelection();
|
||||||
break;
|
return;
|
||||||
case 'Shift':
|
case 'Shift':
|
||||||
break;
|
return;
|
||||||
default:
|
}
|
||||||
|
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 ']': editor.applyStyleToSelection({size_add:1}); return;
|
||||||
|
case '[': editor.applyStyleToSelection({size_add:-1}); return;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!e.ctrlKey && !e.metaKey) {
|
if (!e.ctrlKey && !e.metaKey) {
|
||||||
e.preventDefault(); // at least needed for 'space'
|
e.preventDefault(); // at least needed for 'space'
|
||||||
editor.insert(e.key);
|
editor.insert(e.key);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('para2').addEventListener('pointermove', interact);
|
document.getElementById('para2').addEventListener('pointermove', interact);
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
function LOG(...args) {
|
||||||
|
// comment out for non-debugging
|
||||||
|
// console.log(args);
|
||||||
|
}
|
||||||
|
|
||||||
function MakeCursor(CanvasKit) {
|
function MakeCursor(CanvasKit) {
|
||||||
const linePaint = new CanvasKit.Paint();
|
const linePaint = new CanvasKit.Paint();
|
||||||
linePaint.setColor([0,0,1,1]);
|
linePaint.setColor([0,0,1,1]);
|
||||||
@ -181,6 +186,12 @@ function make_default_paint() {
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function make_default_font(tf) {
|
||||||
|
const font = new CanvasKit.Font(tf);
|
||||||
|
font.setSubpixel(true);
|
||||||
|
return font;
|
||||||
|
}
|
||||||
|
|
||||||
function MakeStyle(length) {
|
function MakeStyle(length) {
|
||||||
return {
|
return {
|
||||||
_length: length,
|
_length: length,
|
||||||
@ -190,6 +201,14 @@ function MakeStyle(length) {
|
|||||||
bold: null,
|
bold: null,
|
||||||
italic: null,
|
italic: null,
|
||||||
|
|
||||||
|
_check_toggle: function(src, dst) {
|
||||||
|
if (src == 'toggle') {
|
||||||
|
return !dst;
|
||||||
|
} else {
|
||||||
|
return src;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// returns true if we changed something affecting layout
|
// returns true if we changed something affecting layout
|
||||||
mergeFrom: function(src) {
|
mergeFrom: function(src) {
|
||||||
let layoutChanged = false;
|
let layoutChanged = false;
|
||||||
@ -203,8 +222,18 @@ function MakeStyle(length) {
|
|||||||
layoutChanged = true;
|
layoutChanged = true;
|
||||||
}
|
}
|
||||||
if (src.color) { this.color = src.color; }
|
if (src.color) { this.color = src.color; }
|
||||||
if (src.bold) { this.bold = src.bold; }
|
|
||||||
if (src.italic) { this.italic = src.italic; }
|
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.size_add) {
|
||||||
|
this.size += src.size_add;
|
||||||
|
layoutChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
return layoutChanged;
|
return layoutChanged;
|
||||||
}
|
}
|
||||||
@ -223,7 +252,7 @@ function MakeEditor(text, style, cursor, width) {
|
|||||||
_X: 0,
|
_X: 0,
|
||||||
_Y: 0,
|
_Y: 0,
|
||||||
_paint: make_default_paint(),
|
_paint: make_default_paint(),
|
||||||
_font: new CanvasKit.Font(style.typeface),
|
_font: make_default_font(style.typeface),
|
||||||
|
|
||||||
getLines: function() { return this._lines; },
|
getLines: function() { return this._lines; },
|
||||||
|
|
||||||
@ -241,17 +270,26 @@ function MakeEditor(text, style, cursor, width) {
|
|||||||
this._Y = y;
|
this._Y = y;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_rebuild_selection: function() {
|
||||||
|
const a = this._index.start;
|
||||||
|
const b = this._index.end;
|
||||||
|
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) {
|
setIndex: function(i) {
|
||||||
this._index.start = this._index.end = i;
|
this._index.start = this._index.end = i;
|
||||||
const l = lines_index_to_line(this._lines, i);
|
this._rebuild_selection();
|
||||||
const x = runs_index_to_x(l.runs, i);
|
|
||||||
this._cursor.place(x, l.top, l.bottom);
|
|
||||||
},
|
},
|
||||||
setIndices: function(a, b) {
|
setIndices: function(a, b) {
|
||||||
if (a > b) { [a, b] = [b, a]; }
|
if (a > b) { [a, b] = [b, a]; }
|
||||||
this._index.start = a;
|
this._index.start = a;
|
||||||
this._index.end = b;
|
this._index.end = b;
|
||||||
this._cursor.setPath(lines_indices_to_path(this._lines, a, b, this._width));
|
this._rebuild_selection();
|
||||||
},
|
},
|
||||||
moveDX: function(dx) {
|
moveDX: function(dx) {
|
||||||
let index;
|
let index;
|
||||||
@ -302,9 +340,11 @@ function MakeEditor(text, style, cursor, width) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
_buildLines: function() {
|
_buildLines: function() {
|
||||||
|
const build_sparse = true;
|
||||||
const blocks = [];
|
const blocks = [];
|
||||||
let block = null;
|
let block = null;
|
||||||
for (const s of this._styles) {
|
for (const s of this._styles) {
|
||||||
|
if (build_sparse) {
|
||||||
if (!block || (block.typeface === s.typeface && block.size === s.size)) {
|
if (!block || (block.typeface === s.typeface && block.size === s.size)) {
|
||||||
if (!block) {
|
if (!block) {
|
||||||
block = { length: 0, typeface: s.typeface, size: s.size };
|
block = { length: 0, typeface: s.typeface, size: s.size };
|
||||||
@ -314,12 +354,28 @@ function MakeEditor(text, style, cursor, width) {
|
|||||||
blocks.push(block);
|
blocks.push(block);
|
||||||
block = { length: s._length, typeface: s.typeface, size: s.size };
|
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);
|
blocks.push(block);
|
||||||
|
}
|
||||||
this._validateBlocks(blocks);
|
this._validateBlocks(blocks);
|
||||||
|
|
||||||
console.log('new blocks', blocks);
|
LOG('new blocks', blocks);
|
||||||
this._lines = CanvasKit.ParagraphBuilder.ShapeText(this._text, blocks, this._width);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteSelection: function() {
|
deleteSelection: function() {
|
||||||
@ -348,14 +404,84 @@ function MakeEditor(text, style, cursor, width) {
|
|||||||
draw: function(canvas) {
|
draw: function(canvas) {
|
||||||
canvas.save();
|
canvas.save();
|
||||||
canvas.translate(this._X, this._Y);
|
canvas.translate(this._X, this._Y);
|
||||||
|
|
||||||
this._cursor.draw_before(canvas);
|
this._cursor.draw_before(canvas);
|
||||||
for (const l of this._lines) {
|
|
||||||
for (let r of l.runs) {
|
const runs = this._runs;
|
||||||
// this._font.setTypeface(r.typeface); // r.typeface is always null (for now)
|
const styles = this._styles;
|
||||||
this._font.setSize(r.size);
|
const f = this._font;
|
||||||
canvas.drawGlyphs(r.glyphs, r.positions, 0, 0, this._font, this._paint);
|
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);
|
||||||
|
// 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);
|
||||||
|
pos = pos.slice(glyph_start*2, glyph_end*2);
|
||||||
|
} else {
|
||||||
|
LOG(' use entire glyph run');
|
||||||
|
}
|
||||||
|
canvas.drawGlyphs(gly, pos, 0, 0, f, p);
|
||||||
|
|
||||||
|
start = end;
|
||||||
|
}
|
||||||
|
|
||||||
this._cursor.draw_after(canvas);
|
this._cursor.draw_after(canvas);
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
},
|
},
|
||||||
@ -371,7 +497,7 @@ function MakeEditor(text, style, cursor, width) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('trying to apply', style, start, end);
|
LOG('trying to apply', style, start, end);
|
||||||
let i;
|
let i;
|
||||||
for (i = 0; i < this._styles.length; ++i) {
|
for (i = 0; i < this._styles.length; ++i) {
|
||||||
if (start <= this._styles[i]._length) {
|
if (start <= this._styles[i]._length) {
|
||||||
@ -387,7 +513,7 @@ function MakeEditor(text, style, cursor, width) {
|
|||||||
const ns = Object.assign({}, s);
|
const ns = Object.assign({}, s);
|
||||||
s._length = start;
|
s._length = start;
|
||||||
ns._length -= start;
|
ns._length -= start;
|
||||||
console.log('initial splice', i, start, s._length, ns._length);
|
LOG('initial splice', i, start, s._length, ns._length);
|
||||||
i += 1;
|
i += 1;
|
||||||
this._styles.splice(i, 0, ns);
|
this._styles.splice(i, 0, ns);
|
||||||
end -= start;
|
end -= start;
|
||||||
@ -396,7 +522,7 @@ function MakeEditor(text, style, cursor, width) {
|
|||||||
// merge into any/all whole styles we overlap
|
// merge into any/all whole styles we overlap
|
||||||
let layoutChanged = false;
|
let layoutChanged = false;
|
||||||
while (end >= this._styles[i]._length) {
|
while (end >= this._styles[i]._length) {
|
||||||
console.log('whole run merging for style index', i)
|
LOG('whole run merging for style index', i)
|
||||||
layoutChanged |= this._styles[i].mergeFrom(style);
|
layoutChanged |= this._styles[i].mergeFrom(style);
|
||||||
end -= this._styles[i]._length;
|
end -= this._styles[i]._length;
|
||||||
i += 1;
|
i += 1;
|
||||||
@ -410,20 +536,20 @@ function MakeEditor(text, style, cursor, width) {
|
|||||||
const ns = Object.assign({}, s); // the new first half
|
const ns = Object.assign({}, s); // the new first half
|
||||||
ns._length = end;
|
ns._length = end;
|
||||||
s._length -= end; // trim the (unchanged) tail
|
s._length -= end; // trim the (unchanged) tail
|
||||||
console.log('merging tail', i, ns._length, s._length);
|
LOG('merging tail', i, ns._length, s._length);
|
||||||
layoutChanged |= ns.mergeFrom(style);
|
layoutChanged |= ns.mergeFrom(style);
|
||||||
this._styles.splice(i, 0, ns);
|
this._styles.splice(i, 0, ns);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._validateStyles();
|
this._validateStyles();
|
||||||
console.log('after applying styles', this._styles);
|
LOG('after applying styles', this._styles);
|
||||||
|
|
||||||
if (layoutChanged) {
|
if (layoutChanged) {
|
||||||
this._buildLines();
|
this._buildLines();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
applyStyleToSelection: function(style) {
|
applyStyleToSelection: function(style) {
|
||||||
applyStyleToRange(style, this._index.start, this._index.end);
|
this.applyStyleToRange(style, this._index.start, this._index.end);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user