1cb93a386Sopenharmony_ci 2cb93a386Sopenharmony_cifunction ASSERT(pred) { 3cb93a386Sopenharmony_ci console.assert(pred, 'assert failed'); 4cb93a386Sopenharmony_ci} 5cb93a386Sopenharmony_ci 6cb93a386Sopenharmony_cifunction LOG(...args) { 7cb93a386Sopenharmony_ci // comment out for non-debugging 8cb93a386Sopenharmony_ci// console.log(args); 9cb93a386Sopenharmony_ci} 10cb93a386Sopenharmony_ci 11cb93a386Sopenharmony_cifunction MakeCursor(CanvasKit) { 12cb93a386Sopenharmony_ci const linePaint = new CanvasKit.Paint(); 13cb93a386Sopenharmony_ci linePaint.setColor([0,0,1,1]); 14cb93a386Sopenharmony_ci linePaint.setStyle(CanvasKit.PaintStyle.Stroke); 15cb93a386Sopenharmony_ci linePaint.setStrokeWidth(2); 16cb93a386Sopenharmony_ci linePaint.setAntiAlias(true); 17cb93a386Sopenharmony_ci 18cb93a386Sopenharmony_ci const pathPaint = new CanvasKit.Paint(); 19cb93a386Sopenharmony_ci pathPaint.setColor([0,0,1,0.25]); 20cb93a386Sopenharmony_ci linePaint.setAntiAlias(true); 21cb93a386Sopenharmony_ci 22cb93a386Sopenharmony_ci return { 23cb93a386Sopenharmony_ci _line_paint: linePaint, // wrap in weak-ref so we can delete it? 24cb93a386Sopenharmony_ci _path_paint: pathPaint, 25cb93a386Sopenharmony_ci _x: 0, 26cb93a386Sopenharmony_ci _top: 0, 27cb93a386Sopenharmony_ci _bottom: 0, 28cb93a386Sopenharmony_ci _path: null, // only use x,top,bottom if path is null 29cb93a386Sopenharmony_ci _draws_per_sec: 2, 30cb93a386Sopenharmony_ci 31cb93a386Sopenharmony_ci // pass 0 for no-draw, pass inf. for always on 32cb93a386Sopenharmony_ci setBlinkRate: function(blinks_per_sec) { 33cb93a386Sopenharmony_ci this._draws_per_sec = blinks_per_sec; 34cb93a386Sopenharmony_ci }, 35cb93a386Sopenharmony_ci place: function(x, top, bottom) { 36cb93a386Sopenharmony_ci this._x = x; 37cb93a386Sopenharmony_ci this._top = top; 38cb93a386Sopenharmony_ci this._bottom = bottom; 39cb93a386Sopenharmony_ci 40cb93a386Sopenharmony_ci this.setPath(null); 41cb93a386Sopenharmony_ci }, 42cb93a386Sopenharmony_ci setPath: function(path) { 43cb93a386Sopenharmony_ci if (this._path) { 44cb93a386Sopenharmony_ci this._path.delete(); 45cb93a386Sopenharmony_ci } 46cb93a386Sopenharmony_ci this._path = path; 47cb93a386Sopenharmony_ci }, 48cb93a386Sopenharmony_ci draw_before: function(canvas) { 49cb93a386Sopenharmony_ci if (this._path) { 50cb93a386Sopenharmony_ci canvas.drawPath(this._path, this._path_paint); 51cb93a386Sopenharmony_ci } 52cb93a386Sopenharmony_ci }, 53cb93a386Sopenharmony_ci draw_after: function(canvas) { 54cb93a386Sopenharmony_ci if (this._path) { 55cb93a386Sopenharmony_ci return; 56cb93a386Sopenharmony_ci } 57cb93a386Sopenharmony_ci if (Math.floor(Date.now() * this._draws_per_sec / 1000) & 1) { 58cb93a386Sopenharmony_ci canvas.drawLine(this._x, this._top, this._x, this._bottom, this._line_paint); 59cb93a386Sopenharmony_ci } 60cb93a386Sopenharmony_ci }, 61cb93a386Sopenharmony_ci }; 62cb93a386Sopenharmony_ci} 63cb93a386Sopenharmony_ci 64cb93a386Sopenharmony_cifunction MakeMouse() { 65cb93a386Sopenharmony_ci return { 66cb93a386Sopenharmony_ci _start_x: 0, _start_y: 0, 67cb93a386Sopenharmony_ci _curr_x: 0, _curr_y: 0, 68cb93a386Sopenharmony_ci _active: false, 69cb93a386Sopenharmony_ci 70cb93a386Sopenharmony_ci isActive: function() { 71cb93a386Sopenharmony_ci return this._active; 72cb93a386Sopenharmony_ci }, 73cb93a386Sopenharmony_ci setDown: function(x, y) { 74cb93a386Sopenharmony_ci this._start_x = this._curr_x = x; 75cb93a386Sopenharmony_ci this._start_y = this._curr_y = y; 76cb93a386Sopenharmony_ci this._active = true; 77cb93a386Sopenharmony_ci }, 78cb93a386Sopenharmony_ci setMove: function(x, y) { 79cb93a386Sopenharmony_ci this._curr_x = x; 80cb93a386Sopenharmony_ci this._curr_y = y; 81cb93a386Sopenharmony_ci }, 82cb93a386Sopenharmony_ci setUp: function(x, y) { 83cb93a386Sopenharmony_ci this._curr_x = x; 84cb93a386Sopenharmony_ci this._curr_y = y; 85cb93a386Sopenharmony_ci this._active = false; 86cb93a386Sopenharmony_ci }, 87cb93a386Sopenharmony_ci getPos: function(dx, dy) { 88cb93a386Sopenharmony_ci return [ this._start_x + dx, this._start_y + dy, this._curr_x + dx, this._curr_y + dy ]; 89cb93a386Sopenharmony_ci }, 90cb93a386Sopenharmony_ci }; 91cb93a386Sopenharmony_ci} 92cb93a386Sopenharmony_ci 93cb93a386Sopenharmony_cifunction runs_x_to_index(runs, x) { 94cb93a386Sopenharmony_ci for (const r of runs) { 95cb93a386Sopenharmony_ci for (let i = 1; i < r.offsets.length; i += 1) { 96cb93a386Sopenharmony_ci if (x < r.positions[i*2]) { 97cb93a386Sopenharmony_ci const mid = (r.positions[i*2-2] + r.positions[i*2]) * 0.5; 98cb93a386Sopenharmony_ci if (x <= mid) { 99cb93a386Sopenharmony_ci return r.offsets[i-1]; 100cb93a386Sopenharmony_ci } else { 101cb93a386Sopenharmony_ci return r.offsets[i]; 102cb93a386Sopenharmony_ci } 103cb93a386Sopenharmony_ci } 104cb93a386Sopenharmony_ci } 105cb93a386Sopenharmony_ci } 106cb93a386Sopenharmony_ci const r = runs[runs.length-1]; 107cb93a386Sopenharmony_ci return r.offsets[r.offsets.length-1]; 108cb93a386Sopenharmony_ci} 109cb93a386Sopenharmony_ci 110cb93a386Sopenharmony_cifunction lines_pos_to_index(lines, x, y) { 111cb93a386Sopenharmony_ci if (y < lines[0].top) { 112cb93a386Sopenharmony_ci return 0; 113cb93a386Sopenharmony_ci } 114cb93a386Sopenharmony_ci const l = lines.find((l) => y <= l.bottom); 115cb93a386Sopenharmony_ci return l ? runs_x_to_index(l.runs, x) 116cb93a386Sopenharmony_ci : lines[lines.length - 1].textRange.last; 117cb93a386Sopenharmony_ci} 118cb93a386Sopenharmony_ci 119cb93a386Sopenharmony_cifunction runs_index_to_run(runs, index) { 120cb93a386Sopenharmony_ci const r = runs.find((r) => index <= r.offsets[r.offsets.length-1]); 121cb93a386Sopenharmony_ci // return last if no run is found 122cb93a386Sopenharmony_ci return r ? r : runs[runs.length-1]; 123cb93a386Sopenharmony_ci} 124cb93a386Sopenharmony_ci 125cb93a386Sopenharmony_cifunction runs_index_to_x(runs, index) { 126cb93a386Sopenharmony_ci const r = runs_index_to_run(runs, index); 127cb93a386Sopenharmony_ci const i = r.offsets.findIndex((offset) => index === offset); 128cb93a386Sopenharmony_ci return i >= 0 ? r.positions[i*2] 129cb93a386Sopenharmony_ci : r.positions[r.positions.length-2]; // last x 130cb93a386Sopenharmony_ci} 131cb93a386Sopenharmony_ci 132cb93a386Sopenharmony_cifunction lines_index_to_line_index(lines, index) { 133cb93a386Sopenharmony_ci const l = lines.findIndex((l) => index <= l.textRange.last); 134cb93a386Sopenharmony_ci return l >= 0 ? l : lines.length-1; 135cb93a386Sopenharmony_ci} 136cb93a386Sopenharmony_ci 137cb93a386Sopenharmony_cifunction lines_index_to_line(lines, index) { 138cb93a386Sopenharmony_ci return lines[lines_index_to_line_index(lines, index)]; 139cb93a386Sopenharmony_ci} 140cb93a386Sopenharmony_ci 141cb93a386Sopenharmony_cifunction lines_indices_to_path(lines, a, b, width) { 142cb93a386Sopenharmony_ci if (a == b) { 143cb93a386Sopenharmony_ci return null; 144cb93a386Sopenharmony_ci } 145cb93a386Sopenharmony_ci if (a > b) { [a, b] = [b, a]; } 146cb93a386Sopenharmony_ci 147cb93a386Sopenharmony_ci const path = new CanvasKit.Path(); 148cb93a386Sopenharmony_ci const la = lines_index_to_line(lines, a); 149cb93a386Sopenharmony_ci const lb = lines_index_to_line(lines, b); 150cb93a386Sopenharmony_ci const ax = runs_index_to_x(la.runs, a); 151cb93a386Sopenharmony_ci const bx = runs_index_to_x(lb.runs, b); 152cb93a386Sopenharmony_ci if (la == lb) { 153cb93a386Sopenharmony_ci path.addRect([ax, la.top, bx, la.bottom]); 154cb93a386Sopenharmony_ci } else { 155cb93a386Sopenharmony_ci path.addRect([ax, la.top, width, la.bottom]); 156cb93a386Sopenharmony_ci path.addRect([0, lb.top, bx, lb.bottom]); 157cb93a386Sopenharmony_ci if (la.bottom < lb.top) { 158cb93a386Sopenharmony_ci path.addRect([0, la.bottom, width, lb.top]); // extra lines inbetween 159cb93a386Sopenharmony_ci } 160cb93a386Sopenharmony_ci } 161cb93a386Sopenharmony_ci return path; 162cb93a386Sopenharmony_ci} 163cb93a386Sopenharmony_ci 164cb93a386Sopenharmony_cifunction string_del(str, start, end) { 165cb93a386Sopenharmony_ci return str.slice(0, start) + str.slice(end, str.length); 166cb93a386Sopenharmony_ci} 167cb93a386Sopenharmony_ci 168cb93a386Sopenharmony_cifunction make_default_paint() { 169cb93a386Sopenharmony_ci const p = new CanvasKit.Paint(); 170cb93a386Sopenharmony_ci p.setAntiAlias(true); 171cb93a386Sopenharmony_ci return p; 172cb93a386Sopenharmony_ci} 173cb93a386Sopenharmony_ci 174cb93a386Sopenharmony_cifunction make_default_font(tf) { 175cb93a386Sopenharmony_ci const font = new CanvasKit.Font(tf); 176cb93a386Sopenharmony_ci font.setSubpixel(true); 177cb93a386Sopenharmony_ci return font; 178cb93a386Sopenharmony_ci} 179cb93a386Sopenharmony_ci 180cb93a386Sopenharmony_cifunction MakeStyle(length) { 181cb93a386Sopenharmony_ci return { 182cb93a386Sopenharmony_ci _length: length, 183cb93a386Sopenharmony_ci typeface: null, 184cb93a386Sopenharmony_ci size: null, 185cb93a386Sopenharmony_ci color: null, 186cb93a386Sopenharmony_ci bold: null, 187cb93a386Sopenharmony_ci italic: null, 188cb93a386Sopenharmony_ci 189cb93a386Sopenharmony_ci _check_toggle: function(src, dst) { 190cb93a386Sopenharmony_ci if (src == 'toggle') { 191cb93a386Sopenharmony_ci return !dst; 192cb93a386Sopenharmony_ci } else { 193cb93a386Sopenharmony_ci return src; 194cb93a386Sopenharmony_ci } 195cb93a386Sopenharmony_ci }, 196cb93a386Sopenharmony_ci 197cb93a386Sopenharmony_ci // returns true if we changed something affecting layout 198cb93a386Sopenharmony_ci mergeFrom: function(src) { 199cb93a386Sopenharmony_ci let layoutChanged = false; 200cb93a386Sopenharmony_ci 201cb93a386Sopenharmony_ci if (src.typeface && this.typeface !== src.typeface) { 202cb93a386Sopenharmony_ci this.typeface = src.typeface; 203cb93a386Sopenharmony_ci layoutChanged = true; 204cb93a386Sopenharmony_ci } 205cb93a386Sopenharmony_ci if (src.size && this.size !== src.size) { 206cb93a386Sopenharmony_ci this.size = src.size; 207cb93a386Sopenharmony_ci layoutChanged = true; 208cb93a386Sopenharmony_ci } 209cb93a386Sopenharmony_ci if (src.color) { 210cb93a386Sopenharmony_ci this.color = src.color; 211cb93a386Sopenharmony_ci delete this.shaderIndex; // we implicitly delete shader if there is a color 212cb93a386Sopenharmony_ci } 213cb93a386Sopenharmony_ci 214cb93a386Sopenharmony_ci if (src.bold) { 215cb93a386Sopenharmony_ci this.bold = this._check_toggle(src.bold, this.bold); 216cb93a386Sopenharmony_ci } 217cb93a386Sopenharmony_ci if (src.italic) { 218cb93a386Sopenharmony_ci this.italic = this._check_toggle(src.italic, this.italic); 219cb93a386Sopenharmony_ci } 220cb93a386Sopenharmony_ci if (src.wavy) { 221cb93a386Sopenharmony_ci this.wavy = this._check_toggle(src.wavy, this.wavy); 222cb93a386Sopenharmony_ci } 223cb93a386Sopenharmony_ci 224cb93a386Sopenharmony_ci if (src.size_add) { 225cb93a386Sopenharmony_ci this.size += src.size_add; 226cb93a386Sopenharmony_ci layoutChanged = true; 227cb93a386Sopenharmony_ci } 228cb93a386Sopenharmony_ci 229cb93a386Sopenharmony_ci if ('shaderIndex' in src) { 230cb93a386Sopenharmony_ci if (src.shaderIndex >= 0) { 231cb93a386Sopenharmony_ci this.shaderIndex = src.shaderIndex; 232cb93a386Sopenharmony_ci } else { 233cb93a386Sopenharmony_ci delete this.shaderIndex; 234cb93a386Sopenharmony_ci } 235cb93a386Sopenharmony_ci } 236cb93a386Sopenharmony_ci return layoutChanged; 237cb93a386Sopenharmony_ci } 238cb93a386Sopenharmony_ci }; 239cb93a386Sopenharmony_ci} 240cb93a386Sopenharmony_ci 241cb93a386Sopenharmony_cifunction MakeEditor(text, style, cursor, width) { 242cb93a386Sopenharmony_ci const ed = { 243cb93a386Sopenharmony_ci _text: text, 244cb93a386Sopenharmony_ci _lines: null, 245cb93a386Sopenharmony_ci _cursor: cursor, 246cb93a386Sopenharmony_ci _width: width, 247cb93a386Sopenharmony_ci _index: { start: 0, end: 0 }, 248cb93a386Sopenharmony_ci _styles: null, 249cb93a386Sopenharmony_ci // drawing 250cb93a386Sopenharmony_ci _X: 0, 251cb93a386Sopenharmony_ci _Y: 0, 252cb93a386Sopenharmony_ci _paint: make_default_paint(), 253cb93a386Sopenharmony_ci _font: make_default_font(style.typeface), 254cb93a386Sopenharmony_ci 255cb93a386Sopenharmony_ci getLines: function() { return this._lines; }, 256cb93a386Sopenharmony_ci 257cb93a386Sopenharmony_ci width: function() { 258cb93a386Sopenharmony_ci return this._width; 259cb93a386Sopenharmony_ci }, 260cb93a386Sopenharmony_ci height: function() { 261cb93a386Sopenharmony_ci return this._lines[this._lines.length-1].bottom; 262cb93a386Sopenharmony_ci }, 263cb93a386Sopenharmony_ci bounds: function() { 264cb93a386Sopenharmony_ci return [this._X, this._Y, this._X + this.width(), this._Y + this.height()]; 265cb93a386Sopenharmony_ci }, 266cb93a386Sopenharmony_ci setXY: function(x, y) { 267cb93a386Sopenharmony_ci this._X = x; 268cb93a386Sopenharmony_ci this._Y = y; 269cb93a386Sopenharmony_ci }, 270cb93a386Sopenharmony_ci 271cb93a386Sopenharmony_ci _rebuild_selection: function() { 272cb93a386Sopenharmony_ci const a = this._index.start; 273cb93a386Sopenharmony_ci const b = this._index.end; 274cb93a386Sopenharmony_ci ASSERT(a >= 0 && a <= b && b <= this._text.length); 275cb93a386Sopenharmony_ci if (a === b) { 276cb93a386Sopenharmony_ci const l = lines_index_to_line(this._lines, a); 277cb93a386Sopenharmony_ci const x = runs_index_to_x(l.runs, a); 278cb93a386Sopenharmony_ci this._cursor.place(x, l.top, l.bottom); 279cb93a386Sopenharmony_ci } else { 280cb93a386Sopenharmony_ci this._cursor.setPath(lines_indices_to_path(this._lines, a, b, this._width)); 281cb93a386Sopenharmony_ci } 282cb93a386Sopenharmony_ci }, 283cb93a386Sopenharmony_ci setIndex: function(i) { 284cb93a386Sopenharmony_ci this._index.start = this._index.end = i; 285cb93a386Sopenharmony_ci this._rebuild_selection(); 286cb93a386Sopenharmony_ci }, 287cb93a386Sopenharmony_ci setIndices: function(a, b) { 288cb93a386Sopenharmony_ci if (a > b) { [a, b] = [b, a]; } 289cb93a386Sopenharmony_ci this._index.start = a; 290cb93a386Sopenharmony_ci this._index.end = b; 291cb93a386Sopenharmony_ci this._rebuild_selection(); 292cb93a386Sopenharmony_ci }, 293cb93a386Sopenharmony_ci moveDX: function(dx) { 294cb93a386Sopenharmony_ci let index; 295cb93a386Sopenharmony_ci if (this._index.start == this._index.end) { 296cb93a386Sopenharmony_ci // just adjust and pin 297cb93a386Sopenharmony_ci index = Math.max(Math.min(this._index.start + dx, this._text.length), 0); 298cb93a386Sopenharmony_ci } else { 299cb93a386Sopenharmony_ci // 'deselect' the region, and turn it into just a single index 300cb93a386Sopenharmony_ci index = dx < 0 ? this._index.start : this._index.end; 301cb93a386Sopenharmony_ci } 302cb93a386Sopenharmony_ci this.setIndex(index); 303cb93a386Sopenharmony_ci }, 304cb93a386Sopenharmony_ci moveDY: function(dy) { 305cb93a386Sopenharmony_ci let index = (dy < 0) ? this._index.start : this._index.end; 306cb93a386Sopenharmony_ci const i = lines_index_to_line_index(this._lines, index); 307cb93a386Sopenharmony_ci if (dy < 0 && i === 0) { 308cb93a386Sopenharmony_ci index = 0; 309cb93a386Sopenharmony_ci } else if (dy > 0 && i == this._lines.length - 1) { 310cb93a386Sopenharmony_ci index = this._text.length; 311cb93a386Sopenharmony_ci } else { 312cb93a386Sopenharmony_ci const x = runs_index_to_x(this._lines[i].runs, index); 313cb93a386Sopenharmony_ci // todo: statefully track "original" x when an up/down sequence started, 314cb93a386Sopenharmony_ci // so we can avoid drift. 315cb93a386Sopenharmony_ci index = runs_x_to_index(this._lines[i+dy].runs, x); 316cb93a386Sopenharmony_ci } 317cb93a386Sopenharmony_ci this.setIndex(index); 318cb93a386Sopenharmony_ci }, 319cb93a386Sopenharmony_ci 320cb93a386Sopenharmony_ci _validateStyles: function() { 321cb93a386Sopenharmony_ci const len = this._styles.reduce((sum, style) => sum + style._length, 0); 322cb93a386Sopenharmony_ci ASSERT(len === this._text.length); 323cb93a386Sopenharmony_ci }, 324cb93a386Sopenharmony_ci _validateBlocks: function(blocks) { 325cb93a386Sopenharmony_ci const len = blocks.reduce((sum, block) => sum + block.length, 0); 326cb93a386Sopenharmony_ci ASSERT(len === this._text.length); 327cb93a386Sopenharmony_ci }, 328cb93a386Sopenharmony_ci 329cb93a386Sopenharmony_ci _buildLines: function() { 330cb93a386Sopenharmony_ci this._validateStyles(); 331cb93a386Sopenharmony_ci 332cb93a386Sopenharmony_ci const blocks = []; 333cb93a386Sopenharmony_ci let block = null; 334cb93a386Sopenharmony_ci for (const s of this._styles) { 335cb93a386Sopenharmony_ci if (!block || (block.typeface === s.typeface && block.size === s.size)) { 336cb93a386Sopenharmony_ci if (!block) { 337cb93a386Sopenharmony_ci block = { length: 0, typeface: s.typeface, size: s.size }; 338cb93a386Sopenharmony_ci } 339cb93a386Sopenharmony_ci block.length += s._length; 340cb93a386Sopenharmony_ci } else { 341cb93a386Sopenharmony_ci blocks.push(block); 342cb93a386Sopenharmony_ci block = { length: s._length, typeface: s.typeface, size: s.size }; 343cb93a386Sopenharmony_ci } 344cb93a386Sopenharmony_ci } 345cb93a386Sopenharmony_ci blocks.push(block); 346cb93a386Sopenharmony_ci this._validateBlocks(blocks); 347cb93a386Sopenharmony_ci 348cb93a386Sopenharmony_ci this._lines = CanvasKit.ParagraphBuilder.ShapeText(this._text, blocks, this._width); 349cb93a386Sopenharmony_ci this._rebuild_selection(); 350cb93a386Sopenharmony_ci 351cb93a386Sopenharmony_ci // add textRange to each run, to aid in drawing 352cb93a386Sopenharmony_ci this._runs = []; 353cb93a386Sopenharmony_ci for (const l of this._lines) { 354cb93a386Sopenharmony_ci for (const r of l.runs) { 355cb93a386Sopenharmony_ci r.textRange = { start: r.offsets[0], end: r.offsets[r.offsets.length-1] }; 356cb93a386Sopenharmony_ci this._runs.push(r); 357cb93a386Sopenharmony_ci } 358cb93a386Sopenharmony_ci } 359cb93a386Sopenharmony_ci }, 360cb93a386Sopenharmony_ci 361cb93a386Sopenharmony_ci // note: this does not rebuild lines/runs, or update the cursor, 362cb93a386Sopenharmony_ci // but it does edit the text and styles 363cb93a386Sopenharmony_ci // returns true if it deleted anything 364cb93a386Sopenharmony_ci _deleteRange: function(start, end) { 365cb93a386Sopenharmony_ci ASSERT(start >= 0 && end <= this._text.length); 366cb93a386Sopenharmony_ci ASSERT(start <= end); 367cb93a386Sopenharmony_ci if (start === end) { 368cb93a386Sopenharmony_ci return false; 369cb93a386Sopenharmony_ci } 370cb93a386Sopenharmony_ci 371cb93a386Sopenharmony_ci this._delete_style_range(start, end); 372cb93a386Sopenharmony_ci // Do this after shrink styles (we use text.length in an assert) 373cb93a386Sopenharmony_ci this._text = string_del(this._text, start, end); 374cb93a386Sopenharmony_ci }, 375cb93a386Sopenharmony_ci deleteSelection: function(direction) { 376cb93a386Sopenharmony_ci let start = this._index.start; 377cb93a386Sopenharmony_ci if (start == this._index.end) { 378cb93a386Sopenharmony_ci if (direction < 0) { 379cb93a386Sopenharmony_ci if (start == 0) { 380cb93a386Sopenharmony_ci return; // nothing to do 381cb93a386Sopenharmony_ci } 382cb93a386Sopenharmony_ci this._deleteRange(start - 1, start); 383cb93a386Sopenharmony_ci start -= 1; 384cb93a386Sopenharmony_ci } else { 385cb93a386Sopenharmony_ci if (start >= this._text.length) { 386cb93a386Sopenharmony_ci return; // nothing to do 387cb93a386Sopenharmony_ci } 388cb93a386Sopenharmony_ci this._deleteRange(start, start + 1); 389cb93a386Sopenharmony_ci } 390cb93a386Sopenharmony_ci } else { 391cb93a386Sopenharmony_ci this._deleteRange(start, this._index.end); 392cb93a386Sopenharmony_ci } 393cb93a386Sopenharmony_ci this._index.start = this._index.end = start; 394cb93a386Sopenharmony_ci this._buildLines(); 395cb93a386Sopenharmony_ci }, 396cb93a386Sopenharmony_ci insert: function(charcode) { 397cb93a386Sopenharmony_ci const len = charcode.length; 398cb93a386Sopenharmony_ci if (this._index.start != this._index.end) { 399cb93a386Sopenharmony_ci this.deleteSelection(); 400cb93a386Sopenharmony_ci } 401cb93a386Sopenharmony_ci const index = this._index.start; 402cb93a386Sopenharmony_ci 403cb93a386Sopenharmony_ci // do this before edit the text (we use text.length in an assert) 404cb93a386Sopenharmony_ci const [i, prev_len] = this.find_style_index_and_prev_length(index); 405cb93a386Sopenharmony_ci this._styles[i]._length += len; 406cb93a386Sopenharmony_ci 407cb93a386Sopenharmony_ci // now grow the text 408cb93a386Sopenharmony_ci this._text = this._text.slice(0, index) + charcode + this._text.slice(index); 409cb93a386Sopenharmony_ci 410cb93a386Sopenharmony_ci this._index.start = this._index.end = index + len; 411cb93a386Sopenharmony_ci this._buildLines(); 412cb93a386Sopenharmony_ci }, 413cb93a386Sopenharmony_ci 414cb93a386Sopenharmony_ci draw: function(canvas, shaders) { 415cb93a386Sopenharmony_ci canvas.save(); 416cb93a386Sopenharmony_ci canvas.translate(this._X, this._Y); 417cb93a386Sopenharmony_ci 418cb93a386Sopenharmony_ci this._cursor.draw_before(canvas); 419cb93a386Sopenharmony_ci 420cb93a386Sopenharmony_ci const runs = this._runs; 421cb93a386Sopenharmony_ci const styles = this._styles; 422cb93a386Sopenharmony_ci const f = this._font; 423cb93a386Sopenharmony_ci const p = this._paint; 424cb93a386Sopenharmony_ci 425cb93a386Sopenharmony_ci let s = styles[0]; 426cb93a386Sopenharmony_ci let sindex = 0; 427cb93a386Sopenharmony_ci let s_start = 0; 428cb93a386Sopenharmony_ci let s_end = s._length; 429cb93a386Sopenharmony_ci 430cb93a386Sopenharmony_ci let r = runs[0]; 431cb93a386Sopenharmony_ci let rindex = 0; 432cb93a386Sopenharmony_ci 433cb93a386Sopenharmony_ci let start = 0; 434cb93a386Sopenharmony_ci let end = 0; 435cb93a386Sopenharmony_ci while (start < this._text.length) { 436cb93a386Sopenharmony_ci while (r.textRange.end <= start) { 437cb93a386Sopenharmony_ci r = runs[++rindex]; 438cb93a386Sopenharmony_ci if (!r) { 439cb93a386Sopenharmony_ci // ran out of runs, so the remaining text must just be WS 440cb93a386Sopenharmony_ci break; 441cb93a386Sopenharmony_ci } 442cb93a386Sopenharmony_ci } 443cb93a386Sopenharmony_ci if (!r) break; 444cb93a386Sopenharmony_ci while (s_end <= start) { 445cb93a386Sopenharmony_ci s = styles[++sindex]; 446cb93a386Sopenharmony_ci s_start = s_end; 447cb93a386Sopenharmony_ci s_end += s._length; 448cb93a386Sopenharmony_ci } 449cb93a386Sopenharmony_ci end = Math.min(r.textRange.end, s_end); 450cb93a386Sopenharmony_ci 451cb93a386Sopenharmony_ci LOG('New range: ', start, end, 452cb93a386Sopenharmony_ci 'from run', r.textRange.start, r.textRange.end, 453cb93a386Sopenharmony_ci 'style', s_start, s_end); 454cb93a386Sopenharmony_ci 455cb93a386Sopenharmony_ci // check that we have anything to draw 456cb93a386Sopenharmony_ci if (r.textRange.start >= end) { 457cb93a386Sopenharmony_ci start = end; 458cb93a386Sopenharmony_ci continue; // could be a span of WS with no glyphs 459cb93a386Sopenharmony_ci } 460cb93a386Sopenharmony_ci 461cb93a386Sopenharmony_ci// f.setTypeface(r.typeface); // r.typeface is always null (for now) 462cb93a386Sopenharmony_ci f.setSize(r.size); 463cb93a386Sopenharmony_ci f.setEmbolden(s.bold); 464cb93a386Sopenharmony_ci f.setSkewX(s.italic ? -0.2 : 0); 465cb93a386Sopenharmony_ci p.setColor(s.color ? s.color : [0,0,0,1]); 466cb93a386Sopenharmony_ci p.setShader(s.shaderIndex >= 0 ? shaders[s.shaderIndex] : null); 467cb93a386Sopenharmony_ci 468cb93a386Sopenharmony_ci let gly = r.glyphs; 469cb93a386Sopenharmony_ci let pos = r.positions; 470cb93a386Sopenharmony_ci if (start > r.textRange.start || end < r.textRange.end) { 471cb93a386Sopenharmony_ci // search for the subset of glyphs to draw 472cb93a386Sopenharmony_ci let glyph_start, glyph_end; 473cb93a386Sopenharmony_ci for (let i = 0; i < r.offsets.length; ++i) { 474cb93a386Sopenharmony_ci if (r.offsets[i] >= start) { 475cb93a386Sopenharmony_ci glyph_start = i; 476cb93a386Sopenharmony_ci break; 477cb93a386Sopenharmony_ci } 478cb93a386Sopenharmony_ci } 479cb93a386Sopenharmony_ci for (let i = glyph_start+1; i < r.offsets.length; ++i) { 480cb93a386Sopenharmony_ci if (r.offsets[i] >= end) { 481cb93a386Sopenharmony_ci glyph_end = i; 482cb93a386Sopenharmony_ci break; 483cb93a386Sopenharmony_ci } 484cb93a386Sopenharmony_ci } 485cb93a386Sopenharmony_ci LOG(' glyph subrange', glyph_start, glyph_end); 486cb93a386Sopenharmony_ci gly = gly.slice(glyph_start, glyph_end); 487cb93a386Sopenharmony_ci pos = pos.slice(glyph_start*2, glyph_end*2); 488cb93a386Sopenharmony_ci } else { 489cb93a386Sopenharmony_ci LOG(' use entire glyph run'); 490cb93a386Sopenharmony_ci } 491cb93a386Sopenharmony_ci 492cb93a386Sopenharmony_ci let working_pos = pos; 493cb93a386Sopenharmony_ci if (s.wavy) { 494cb93a386Sopenharmony_ci const xscale = 0.05; 495cb93a386Sopenharmony_ci const yscale = r.size * 0.125; 496cb93a386Sopenharmony_ci let wavy = []; 497cb93a386Sopenharmony_ci for (let i = 0; i < pos.length; i += 2) { 498cb93a386Sopenharmony_ci const x = pos[i + 0]; 499cb93a386Sopenharmony_ci wavy.push(x); 500cb93a386Sopenharmony_ci wavy.push(pos[i + 1] + Math.sin(x * xscale) * yscale); 501cb93a386Sopenharmony_ci } 502cb93a386Sopenharmony_ci working_pos = new Float32Array(wavy); 503cb93a386Sopenharmony_ci } 504cb93a386Sopenharmony_ci canvas.drawGlyphs(gly, working_pos, 0, 0, f, p); 505cb93a386Sopenharmony_ci 506cb93a386Sopenharmony_ci p.setShader(null); // in case our caller deletes their shader(s) 507cb93a386Sopenharmony_ci 508cb93a386Sopenharmony_ci start = end; 509cb93a386Sopenharmony_ci } 510cb93a386Sopenharmony_ci 511cb93a386Sopenharmony_ci this._cursor.draw_after(canvas); 512cb93a386Sopenharmony_ci canvas.restore(); 513cb93a386Sopenharmony_ci }, 514cb93a386Sopenharmony_ci 515cb93a386Sopenharmony_ci // Styling 516cb93a386Sopenharmony_ci 517cb93a386Sopenharmony_ci // returns [index, prev total length before this style] 518cb93a386Sopenharmony_ci find_style_index_and_prev_length: function(index) { 519cb93a386Sopenharmony_ci let len = 0; 520cb93a386Sopenharmony_ci for (let i = 0; i < this._styles.length; ++i) { 521cb93a386Sopenharmony_ci const l = this._styles[i]._length; 522cb93a386Sopenharmony_ci len += l; 523cb93a386Sopenharmony_ci // < favors the latter style if index is between two styles 524cb93a386Sopenharmony_ci if (index < len) { 525cb93a386Sopenharmony_ci return [i, len - l]; 526cb93a386Sopenharmony_ci } 527cb93a386Sopenharmony_ci } 528cb93a386Sopenharmony_ci ASSERT(len === this._text.length); 529cb93a386Sopenharmony_ci return [this._styles.length-1, len]; 530cb93a386Sopenharmony_ci }, 531cb93a386Sopenharmony_ci _delete_style_range: function(start, end) { 532cb93a386Sopenharmony_ci // shrink/remove styles 533cb93a386Sopenharmony_ci // 534cb93a386Sopenharmony_ci // [.....][....][....][.....] styles 535cb93a386Sopenharmony_ci // [..................] start...end 536cb93a386Sopenharmony_ci // 537cb93a386Sopenharmony_ci // - trim the first style 538cb93a386Sopenharmony_ci // - remove the middle styles 539cb93a386Sopenharmony_ci // - trim the last style 540cb93a386Sopenharmony_ci 541cb93a386Sopenharmony_ci let N = end - start; 542cb93a386Sopenharmony_ci let [i, prev_len] = this.find_style_index_and_prev_length(start); 543cb93a386Sopenharmony_ci let s = this._styles[i]; 544cb93a386Sopenharmony_ci if (start > prev_len) { 545cb93a386Sopenharmony_ci // we overlap the first style (but not entirely 546cb93a386Sopenharmony_ci const skip = start - prev_len; 547cb93a386Sopenharmony_ci ASSERT(skip < s._length); 548cb93a386Sopenharmony_ci const shrink = Math.min(N, s._length - skip); 549cb93a386Sopenharmony_ci ASSERT(shrink > 0); 550cb93a386Sopenharmony_ci s._length -= shrink; 551cb93a386Sopenharmony_ci N -= shrink; 552cb93a386Sopenharmony_ci if (N === 0) { 553cb93a386Sopenharmony_ci return; 554cb93a386Sopenharmony_ci } 555cb93a386Sopenharmony_ci i += 1; 556cb93a386Sopenharmony_ci ASSERT(i < this._styles.length); 557cb93a386Sopenharmony_ci } 558cb93a386Sopenharmony_ci while (N > 0) { 559cb93a386Sopenharmony_ci s = this._styles[i]; 560cb93a386Sopenharmony_ci if (N >= s._length) { 561cb93a386Sopenharmony_ci N -= s._length; 562cb93a386Sopenharmony_ci this._styles.splice(i, 1); 563cb93a386Sopenharmony_ci } else { 564cb93a386Sopenharmony_ci s._length -= N; 565cb93a386Sopenharmony_ci break; 566cb93a386Sopenharmony_ci } 567cb93a386Sopenharmony_ci } 568cb93a386Sopenharmony_ci }, 569cb93a386Sopenharmony_ci 570cb93a386Sopenharmony_ci applyStyleToRange: function(style, start, end) { 571cb93a386Sopenharmony_ci if (start > end) { [start, end] = [end, start]; } 572cb93a386Sopenharmony_ci ASSERT(start >= 0 && end <= this._text.length); 573cb93a386Sopenharmony_ci if (start === end) { 574cb93a386Sopenharmony_ci return; 575cb93a386Sopenharmony_ci } 576cb93a386Sopenharmony_ci 577cb93a386Sopenharmony_ci LOG('trying to apply', style, start, end); 578cb93a386Sopenharmony_ci let i; 579cb93a386Sopenharmony_ci for (i = 0; i < this._styles.length; ++i) { 580cb93a386Sopenharmony_ci if (start <= this._styles[i]._length) { 581cb93a386Sopenharmony_ci break; 582cb93a386Sopenharmony_ci } 583cb93a386Sopenharmony_ci start -= this._styles[i]._length; 584cb93a386Sopenharmony_ci end -= this._styles[i]._length; 585cb93a386Sopenharmony_ci } 586cb93a386Sopenharmony_ci 587cb93a386Sopenharmony_ci let s = this._styles[i]; 588cb93a386Sopenharmony_ci // do we need to fission off a clean subset for the head of s? 589cb93a386Sopenharmony_ci if (start > 0) { 590cb93a386Sopenharmony_ci const ns = Object.assign({}, s); 591cb93a386Sopenharmony_ci s._length = start; 592cb93a386Sopenharmony_ci ns._length -= start; 593cb93a386Sopenharmony_ci LOG('initial splice', i, start, s._length, ns._length); 594cb93a386Sopenharmony_ci i += 1; 595cb93a386Sopenharmony_ci this._styles.splice(i, 0, ns); 596cb93a386Sopenharmony_ci end -= start; 597cb93a386Sopenharmony_ci // we don't use start any more 598cb93a386Sopenharmony_ci } 599cb93a386Sopenharmony_ci // merge into any/all whole styles we overlap 600cb93a386Sopenharmony_ci let layoutChanged = false; 601cb93a386Sopenharmony_ci while (end >= this._styles[i]._length) { 602cb93a386Sopenharmony_ci LOG('whole run merging for style index', i) 603cb93a386Sopenharmony_ci layoutChanged |= this._styles[i].mergeFrom(style); 604cb93a386Sopenharmony_ci end -= this._styles[i]._length; 605cb93a386Sopenharmony_ci i += 1; 606cb93a386Sopenharmony_ci if (end == 0) { 607cb93a386Sopenharmony_ci break; 608cb93a386Sopenharmony_ci } 609cb93a386Sopenharmony_ci } 610cb93a386Sopenharmony_ci // do we partially cover the last run 611cb93a386Sopenharmony_ci if (end > 0) { 612cb93a386Sopenharmony_ci s = this._styles[i]; 613cb93a386Sopenharmony_ci const ns = Object.assign({}, s); // the new first half 614cb93a386Sopenharmony_ci ns._length = end; 615cb93a386Sopenharmony_ci s._length -= end; // trim the (unchanged) tail 616cb93a386Sopenharmony_ci LOG('merging tail', i, ns._length, s._length); 617cb93a386Sopenharmony_ci layoutChanged |= ns.mergeFrom(style); 618cb93a386Sopenharmony_ci this._styles.splice(i, 0, ns); 619cb93a386Sopenharmony_ci } 620cb93a386Sopenharmony_ci 621cb93a386Sopenharmony_ci this._validateStyles(); 622cb93a386Sopenharmony_ci LOG('after applying styles', this._styles); 623cb93a386Sopenharmony_ci 624cb93a386Sopenharmony_ci if (layoutChanged) { 625cb93a386Sopenharmony_ci this._buildLines(); 626cb93a386Sopenharmony_ci } 627cb93a386Sopenharmony_ci }, 628cb93a386Sopenharmony_ci applyStyleToSelection: function(style) { 629cb93a386Sopenharmony_ci this.applyStyleToRange(style, this._index.start, this._index.end); 630cb93a386Sopenharmony_ci }, 631cb93a386Sopenharmony_ci }; 632cb93a386Sopenharmony_ci 633cb93a386Sopenharmony_ci const s = MakeStyle(ed._text.length); 634cb93a386Sopenharmony_ci s.mergeFrom(style); 635cb93a386Sopenharmony_ci ed._styles = [ s ]; 636cb93a386Sopenharmony_ci ed._buildLines(); 637cb93a386Sopenharmony_ci return ed; 638cb93a386Sopenharmony_ci} 639