1/* Universal Module Definition */ 2(function (root, factory) { 3 if (typeof define === 'function' && define.amd) { 4 // AMD. Register as a named AMD module. 5 define('baselineRatio', [], function () { 6 return (root.baselineRatio = factory()); 7 }); 8 } else if (typeof exports === 'object') { 9 // Node. Does not work with strict CommonJS, but 10 // only CommonJS-like enviroments that support module.exports, 11 // like Node. 12 module.exports = factory(); 13 } else { 14 // Browser globals 15 root.baselineRatio = factory(); 16 } 17}(this, function () { 18 var baselineRatio = function(elem) { 19 // Get the baseline in the context of whatever element is passed in. 20 elem = elem || document.body; 21 22 // The container is a little defenseive. 23 var container = document.createElement('div'); 24 container.style.display = "block"; 25 container.style.position = "absolute"; 26 container.style.bottom = "0"; 27 container.style.right = "0"; 28 container.style.width = "0px"; 29 container.style.height = "0px"; 30 container.style.margin = "0"; 31 container.style.padding = "0"; 32 container.style.visibility = "hidden"; 33 container.style.overflow = "hidden"; 34 35 // Intentionally unprotected style definition. 36 var small = document.createElement('span'); 37 var large = document.createElement('span'); 38 39 // Large numbers help improve accuracy. 40 small.style.fontSize = "0px"; 41 large.style.fontSize = "2000px"; 42 43 small.innerHTML = "X"; 44 large.innerHTML = "X"; 45 46 container.appendChild(small); 47 container.appendChild(large); 48 49 // Put the element in the DOM for a split second. 50 elem.appendChild(container); 51 var smalldims = small.getBoundingClientRect(); 52 var largedims = large.getBoundingClientRect(); 53 elem.removeChild(container); 54 55 // Calculate where the baseline was, percentage-wise.
56 var baselineposition = smalldims.top - largedims.top; 57 var height = largedims.height; 58 59 return 1 - (baselineposition / height); 60 } 61 62 return baselineRatio; 63})); 64 65/*! 66 * classie v1.0.1 67 * class helper functions 68 * from bonzo https://github.com/ded/bonzo 69 * MIT license 70 * 71 * classie.has( elem, 'my-class' ) -> true/false 72 * classie.add( elem, 'my-new-class' ) 73 * classie.remove( elem, 'my-unwanted-class' ) 74 * classie.toggle( elem, 'my-class' ) 75 */ 76 77/*jshint browser: true, strict: true, undef: true, unused: true */ 78/*global define: false, module: false */ 79 80( function( window ) { 81 82'use strict'; 83 84// class helper functions from bonzo https://github.com/ded/bonzo 85 86function classReg( className ) { 87 return new RegExp("(^|\\s+)" + className + "(\\s+|$)"); 88} 89 90// classList support for class management 91// altho to be fair, the api sucks because it won't accept multiple classes at once 92var hasClass, addClass, removeClass; 93 94if ( 'classList' in document.documentElement ) { 95 hasClass = function( elem, c ) { 96 return elem.classList.contains( c ); 97 }; 98 addClass = function( elem, c ) { 99 elem.classList.add( c ); 100 }; 101 removeClass = function( elem, c ) { 102 elem.classList.remove( c ); 103 }; 104} 105else { 106 hasClass = function( elem, c ) { 107 return classReg( c ).test( elem.className ); 108 }; 109 addClass = function( elem, c ) { 110 if ( !hasClass( elem, c ) ) { 111 elem.className = elem.className + ' ' + c; 112 } 113 }; 114 removeClass = function( elem, c ) { 115 elem.className = elem.className.replace( classReg( c ), ' ' ); 116 }; 117} 118 119function toggleClass( elem, c ) { 120 var fn = hasClass( elem, c ) ? removeClass : addClass; 121 fn( elem, c ); 122} 123 124var classie = { 125 // full names 126 hasClass: hasClass, 127 addClass: addClass, 128 removeClass: removeClass, 129 toggleClass: toggleClass, 130 // short names 131 has: hasClass, 132 add: addClass, 133 remove: removeClass, 134 toggle: toggleClass 135}; 136 137// transport 138if ( typeof define === 'function' && define.amd ) { 139 // AMD 140 define( classie ); 141} else if ( typeof exports === 'object' ) { 142 // CommonJS 143 module.exports = classie; 144} else { 145 // browser global 146 window.classie = classie; 147} 148 149})( window ); 150 151function MultipleUnderline(element, underlineStyles, elementStyles) { 152 //ctor 153 this.element = element; 154 155 this.text = this.element.textContent; 156 157 this.underlineStyles = underlineStyles; 158 159 // this.elementStyles = getElementStyles(element); 160 this.elementStyles = elementStyles; 161 162 this.canvas = document.createElement("canvas"); 163 this.ctx = this.canvas.getContext('2d'); 164 165 166 this.ratio = window.devicePixelRatio; 167 this.canvas.width = this.elementStyles.width*this.ratio; 168 this.canvas.height = this.elementStyles.height*this.ratio; 169 // this.canvas.height = this.canvas.clientHeight + this.elementStyles.lineHeight; 170 this.canvas.style.left = this.elementStyles.canvasLeft + 'px'; 171 this.element.appendChild(this.canvas); 172 this.canvas.style.width = this.elementStyles.width + 'px'; 173 174 this.ctx.font = this.font = this.elementStyles.fontStyle + ' ' 175 + multiplyValue(this.elementStyles.fontSize, this.ratio) + ' ' 176 + this.elementStyles.fontFamily; 177 178 this.multipleRedrawActive = false; 179 if (is_chrome) { 180 // chrome floor the lineheight when it is not a whole number 181 // this.elementStyles.lineHeight = Math.floor(this.elementStyles.lineHeight * this.ratio); 182 this.elementStyles.lineHeight = this.elementStyles.lineHeight * this.ratio; 183 } else { 184 this.elementStyles.lineHeight = this.elementStyles.lineHeight * this.ratio; 185 } 186 187 // determine the text-underline-width / strokeWidth 188 var dotWidth = this.ctx.measureText('.')['width']; 189 if (this.underlineStyles['text-underline-width'] == "auto") { 190 // if set to auto, calculate the optimized width based on font 191 this.strokeWidth = dotWidth/12; 192 } else { 193 //if set to px value, todo: other unit such as em? 194 this.strokeWidth = this.underlineStyles['text-underline-width']; 195 //get number value 196 this.strokeWidth = parseFloat(this.strokeWidth)*this.ratio; 197 } 198 199 // determine the text-underline-position / underlinePosition 200 // text-underline-position in ratio, todo: default and user set position ratio 201 if (this.underlineStyles['text-underline-position'] == "auto") { 202 // if set to auto, calculate the optimized width based on font 203 this.underlinePosition = parseFloat(this.elementStyles.fontSize) * this.ratio 204 * ( 1 - this.elementStyles.baselinePositionRatio + 205 this.elementStyles.baselinePositionRatio * 0.4) 206 + this.strokeWidth/2; 207 } else { 208 //if set to ratio value, todo: other unit such as em, px? 209 var userUnderlinePosition = parseFloat(this.underlineStyles['text-underline-position']); 210 // console.log(userUnderlinePosition); 211 this.underlinePosition = parseFloat(this.elementStyles.fontSize) * this.ratio * 212 ( 1 - this.elementStyles.baselinePositionRatio + 213 this.elementStyles.baselinePositionRatio * userUnderlinePosition) 214 + this.strokeWidth/2; 215 } 216 217 218 var adjustValue = optimalStrokeWidthPos(this.strokeWidth, this.underlinePosition); 219 this.strokeWidth = adjustValue.strokeWidth; 220 this.underlinePosition = adjustValue.posY; 221 222 this.lines = []; 223 this.myStrings = []; 224 225 var words = this.text.match(/[^\s-]+-?\s?/g); 226 var line = ''; 227 228 var linePositionY = 0; 229 var firstLineCount = 0; 230 for (var n = 0; n < words.length; n++) { 231 // add the whitespace after getting the width measurement 232 if (words[n].match(/\s+$/)) { 233 // the last character of words[n] is whitespace 234 var newWord = words[n].replace(/\s+$/, ''); 235 var testLine = line + newWord; 236 var testLineMetrics = this.ctx.measureText(testLine); 237 var testLineWidth = testLineMetrics.width; 238 testLine = testLine + ' '; 239 } else { 240 var testLine = line + words[n]; 241 var testLineMetrics = this.ctx.measureText(testLine); 242 var testLineWidth = testLineMetrics.width; 243 } 244 245 if (!firstLineCount) { 246 //the first line, should consider startingPointX 247 if (testLineWidth + this.elementStyles.textIndent * this.ratio > this.elementStyles.parentWidth * this.ratio && n > 0) { 248 // draw the underline 249 if (line.match(/\s+$/)) { 250 // the last character of line is whitespace 251 var lineMetrics = this.ctx.measureText(line.replace(/\s+$/, '')); 252 var lineWidth = lineMetrics.width; 253 } else { 254 var lineMetrics = this.ctx.measureText(line); 255 var lineWidth = lineMetrics.width; 256 } 257 258 var tempLine = { 259 lineText: line, 260 lineTextIndent: this.elementStyles.textIndent * this.ratio - 0.2, 261 linePositionY: linePositionY, 262 lineMeasureWidth: lineWidth 263 } 264 this.lines.push(tempLine) 265 266 line = words[n]; 267 linePositionY += this.elementStyles.lineHeight; 268 firstLineCount++; 269 } else { 270 line = testLine; 271 } 272 } else { 273 if (testLineWidth > this.elementStyles.parentWidth * this.ratio && n > 0) { 274 // draw the underline 275 if (line.match(/\s+$/)) { 276 // the last character of line is whitespace 277 var lineMetrics = this.ctx.measureText(line.replace(/\s+$/, '')); 278 var lineWidth = lineMetrics.width; 279 } else { 280 var lineMetrics = this.ctx.measureText(line); 281 var lineWidth = lineMetrics.width; 282 } 283 284 var tempLine = { 285 lineText: line, 286 lineTextIndent: -0.2, 287 linePositionY: linePositionY, 288 lineMeasureWidth: lineWidth 289 } 290 this.lines.push(tempLine); 291 292 line = words[n]; 293 linePositionY += this.elementStyles.lineHeight; 294 } else { 295 line = testLine; 296 } 297 } 298 } 299 // draw the last line 300 // draw the underline 301 if (line.match(/\s+$/)) { 302 // the last character of line is whitespace 303 var lineMetrics = this.ctx.measureText(line.replace(/\s+$/, '')); 304 var lineWidth = lineMetrics.width; 305 } else { 306 var lineMetrics = this.ctx.measureText(line); 307 var lineWidth = lineMetrics.width; 308 } 309 310 var tempLine = { 311 lineText: line, 312 lineTextIndent: -0.2, 313 linePositionY: linePositionY, 314 lineMeasureWidth: lineWidth 315 } 316 this.lines.push(tempLine); 317 for(var i = 0; i < this.lines.length; i++) { 318 var tempLine = this.lines[i]; 319 var myString = new GuitarString( 320 this.ctx, 321 new Point(tempLine.lineTextIndent, tempLine.linePositionY + this.underlinePosition), 322 new Point(tempLine.lineTextIndent + tempLine.lineMeasureWidth, tempLine.linePositionY + this.underlinePosition), 323 this.strokeWidth, this.underlineStyles['text-underline-color'], this.ratio); 324 this.myStrings.push(myString); 325 } 326 327 this.drawUnderline(); 328 this.drawHoles(); 329 330} 331 332MultipleUnderline.prototype.drawUnderline = function(){ 333 // draw the underline 334 for(var i = 0; i < this.myStrings.length; i++) { 335 var tempString = this.myStrings[i]; 336 // tempString.clear();
337 tempString.update(); 338 tempString.draw(); 339 } 340 341}; 342 343 344MultipleUnderline.prototype.drawHoles = function(){ 345 // draw the font stroke 346 for(var i = 0; i < this.lines.length; i++) { 347 var tempLine = this.lines[i]; 348 349 this.ctx.globalCompositeOperation = "destination-out"; 350 this.ctx.font = this.font; 351 352 this.ctx.fillStyle = 'green'; 353 this.ctx.textBaseline = 'top'; 354 this.ctx.fillText(tempLine.lineText, tempLine.lineTextIndent, tempLine.linePositionY); 355 356 this.ctx.lineWidth = 2*this.ratio + this.strokeWidth*3.6; 357 this.ctx.strokeStyle = 'blue'; 358 this.ctx.strokeText(tempLine.lineText, tempLine.lineTextIndent, tempLine.linePositionY); 359 360 } 361} 362 363MultipleUnderline.prototype.clear = function(){ 364 // clear 365 var lastMultipleRedrawActive = this.multipleRedrawActive; 366 this.multipleRedrawActive = false; 367 for(var i = 0; i < this.myStrings.length; i++) { 368 var tempString = this.myStrings[i]; 369 // this.myString.clear(); 370 // console.log(tempString.redrawActive); 371 if(tempString.redrawActive) { 372 this.multipleRedrawActive = true; 373 } 374 } 375 // console.log(this.multipleRedrawActive); 376 if (this.multipleRedrawActive) { 377 console.log('clear now!') 378 this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); 379 } 380 // if (!lastMultipleRedrawActive && this.multipleRedrawActive) { 381 // for(var i = 0; i < this.myStrings.length; i++) { 382 // var tempString = this.myStrings[i]; 383 // tempString.drawLine(); 384 // } 385 // } 386 387}; 388 389MultipleUnderline.prototype.update = function(){ 390 //update 391}; 392 393 394MultipleUnderline.prototype.draw = function(){ 395 // draw 396 if (this.multipleRedrawActive) { 397 this.drawUnderline(); 398 this.drawHoles(); 399 } 400}; 401 402function MultipleUnderline(element, underlineStyles, elementStyles) { 403 //ctor 404 this.element = element; 405 406 this.text = this.element.textContent; 407 408 this.underlineStyles = underlineStyles; 409 410 // this.elementStyles = getElementStyles(element); 411 this.elementStyles = elementStyles; 412 413 this.canvas = document.createElement("canvas"); 414 this.canvas.width = this.elementStyles.width; 415 this.canvas.height = this.elementStyles.height; 416 this.canvas.style.left = this.elementStyles.canvasLeft + 'px'; 417 this.element.appendChild(this.canvas); 418 this.canvas.width = this.canvas.clientWidth; 419 this.canvas.height = this.canvas.clientHeight + this.elementStyles.lineHeight; 420 421 this.ctx = this.canvas.getContext('2d'); 422 this.ctx.font = this.font = this.elementStyles.fontStyle + ' ' + this.elementStyles.fontSize + ' ' + this.elementStyles.fontFamily; 423 424 this.multipleRedrawActive = false; 425 if (is_chrome) { 426 // chrome floor the lineheight when it is not a whole number 427 this.elementStyles.lineHeight = Math.floor(this.elementStyles.lineHeight); 428 } 429 430 431 // determine the text-underline-width / strokeWidth 432 this.dotWidth = this.ctx.measureText('.')['width']; 433 if (this.underlineStyles['text-underline-width'] == "auto") { 434 // if set to auto, calculate the optimized width based on font 435 if (this.dotWidth / 6 <= 2) { 436 this.strokeWidth = Math.round(this.dotWidth / 3) / 2; 437 } else { 438 this.strokeWidth = Math.round(this.dotWidth / 6); 439 } 440 } else { 441 //if set to px value 442 this.strokeWidth = this.underlineStyles['text-underline-width']; 443 //get number value 444 this.strokeWidth = parseFloat(this.strokeWidth); 445 } 446 447 // determine the text-underline-position / underlinePosition 448 // text-underline-position in ratio 449 this.underlinePosition = parseFloat(this.elementStyles.fontSize) * 0.89; 450 if (this.strokeWidth <= 1 || (this.strokeWidth % 2 && this.strokeWidth > 2)) { 451 this.underlinePosition = Math.round(this.underlinePosition - 0.5) + 0.5; 452 } else { 453 this.underlinePosition = Math.round(this.underlinePosition); 454 } 455 456 this.lines = []; 457 this.myStrings = []; 458 459 var words = this.text.match(/[^\s-]+-?\s?/g); 460 var line = ''; 461 462 var linePositionY = 0; 463 var firstLineCount = 0; 464 for (var n = 0; n < words.length; n++) { 465 // add the whitespace after getting the width measurement 466 if (words[n].match(/\s+$/)) { 467 // the last character of words[n] is whitespace 468 var newWord = words[n].replace(/\s+$/, ''); 469 var testLine = line + newWord; 470 var testLineMetrics = this.ctx.measureText(testLine); 471 var testLineWidth = testLineMetrics.width; 472 testLine = testLine + ' '; 473 } else { 474 var testLine = line + words[n]; 475 var testLineMetrics = this.ctx.measureText(testLine); 476 var testLineWidth = testLineMetrics.width; 477 } 478 479 if (!firstLineCount) { 480 //the first line, should consider startingPointX 481 if (testLineWidth + this.elementStyles.textIndent > this.elementStyles.parentWidth && n > 0) { 482 // draw the underline 483 if (line.match(/\s+$/)) { 484 // the last character of line is whitespace 485 var lineMetrics = this.ctx.measureText(line.replace(/\s+$/, '')); 486 var lineWidth = lineMetrics.width; 487 } else { 488 var lineMetrics = this.ctx.measureText(line); 489 var lineWidth = lineMetrics.width; 490 } 491 492 var tempLine = { 493 lineText: line, 494 lineTextIndent: this.elementStyles.textIndent, 495 linePositionY: linePositionY, 496 lineMeasureWidth: lineWidth 497 } 498 this.lines.push(tempLine) 499 500 line = words[n]; 501 linePositionY += this.elementStyles.lineHeight; 502 firstLineCount++; 503 } else { 504 line = testLine; 505 } 506 } else { 507 if (testLineWidth > this.elementStyles.parentWidth && n > 0) { 508 // draw the underline 509 if (line.match(/\s+$/)) { 510 // the last character of line is whitespace 511 var lineMetrics = this.ctx.measureText(line.replace(/\s+$/, '')); 512 var lineWidth = lineMetrics.width; 513 } else { 514 var lineMetrics = this.ctx.measureText(line); 515 var lineWidth = lineMetrics.width; 516 } 517 518 var tempLine = { 519 lineText: line, 520 lineTextIndent: 0, 521 linePositionY: linePositionY, 522 lineMeasureWidth: lineWidth 523 } 524 this.lines.push(tempLine); 525 526 line = words[n]; 527 linePositionY += this.elementStyles.lineHeight; 528 } else { 529 line = testLine; 530 } 531 } 532 } 533 // draw the last line 534 // draw the underline 535 if (line.match(/\s+$/)) { 536 // the last character of line is whitespace 537 var lineMetrics = this.ctx.measureText(line.replace(/\s+$/, '')); 538 var lineWidth = lineMetrics.width; 539 } else { 540 var lineMetrics = this.ctx.measureText(line); 541 var lineWidth = lineMetrics.width; 542 } 543 544 var tempLine = { 545 lineText: line, 546 lineTextIndent: 0, 547 linePositionY: linePositionY, 548 lineMeasureWidth: lineWidth 549 } 550 this.lines.push(tempLine); 551 552 553 554 for(var i = 0; i < this.lines.length; i++) { 555 var tempLine = this.lines[i]; 556 var myString = new GuitarString( 557 this.ctx, 558 new Point(tempLine.lineTextIndent, tempLine.linePositionY + this.underlinePosition), 559 new Point(tempLine.lineTextIndent + tempLine.lineMeasureWidth, tempLine.linePositionY + this.underlinePosition), 560 this.strokeWidth, this.underlineStyles['text-underline-color'], 1); 561 this.myStrings.push(myString); 562 } 563 564 this.drawUnderline(); 565 this.drawHoles(); 566 567} 568 569 570MultipleUnderline.prototype.clear = function(){ 571 // clear 572 var lastMultipleRedrawActive = this.multipleRedrawActive; 573 this.multipleRedrawActive = false; 574 for(var i = 0; i < this.myStrings.length; i++) { 575 var tempString = this.myStrings[i]; 576 // this.myString.clear(); 577 // console.log(tempString.redrawActive); 578 if(tempString.redrawActive) { 579 this.multipleRedrawActive = true; 580 } 581 } 582 // console.log(this.multipleRedrawActive); 583 if (this.multipleRedrawActive) { 584 console.log('clear now!') 585 this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); 586 } 587 // if (!lastMultipleRedrawActive && this.multipleRedrawActive) {
588 // for(var i = 0; i < this.myStrings.length; i++) { 589 // var tempString = this.myStrings[i]; 590 // tempString.drawLine(); 591 // } 592 // } 593 594}; 595 596MultipleUnderline.prototype.update = function(){ 597 //update 598}; 599 600 601MultipleUnderline.prototype.draw = function(){ 602 // draw 603 if (this.multipleRedrawActive) { 604 this.drawUnderline(); 605 this.drawHoles(); 606 } 607}; 608 609 610MultipleUnderline.prototype.drawUnderline = function(){ 611 // draw the underline 612 for(var i = 0; i < this.myStrings.length; i++) { 613 var tempString = this.myStrings[i]; 614 // tempString.clear(); 615 tempString.update(); 616 tempString.draw(); 617 } 618 619}; 620 621 622MultipleUnderline.prototype.drawHoles = function(){ 623 // draw the font stroke 624 for(var i = 0; i < this.lines.length; i++) { 625 var tempLine = this.lines[i]; 626 627 this.ctx.globalCompositeOperation = "destination-out"; 628 this.ctx.font = this.font; 629 this.ctx.fillStyle = 'green'; 630 this.ctx.textBaseline = 'top'; 631 this.ctx.fillText(tempLine.lineText, tempLine.lineTextIndent, tempLine.linePositionY); 632 this.ctx.lineWidth = 3 + this.strokeWidth; 633 this.ctx.strokeStyle = 'blue'; 634 this.ctx.strokeText(tempLine.lineText, tempLine.lineTextIndent, tempLine.linePositionY); 635 636 } 637} 638 639var multiplyValue = function(value, multiplier){ 640 var str = value; 641 var m = multiplier; 642 var result = str.match(/(\d*\.?\d*)(.*)/); 643 //http://stackoverflow.com/questions/2868947/split1px-into-1px-1-px-in-javascript 644 return result[1] * m + result[2]; 645} 646 647var optimalStrokeWidthPos = function(strokeWidth, posY){ 648 if ( strokeWidth < 1) { 649 posY = Math.round(posY - 0.5) + 0.5; 650 } else if ( strokeWidth >= 1 ) { 651 strokeWidth = Math.round( strokeWidth ); 652 if ( strokeWidth % 2 ){ 653 // odd, posY -> 0.5 654 posY = Math.round(posY - 0.5) + 0.5; 655 } else { 656 // even, posY -> 1 657 posY = Math.round(posY); 658 } 659 } 660 return { 661 strokeWidth: strokeWidth, 662 posY: posY 663 } 664} 665 666function SingleUnderline(element, underlineStyles, elementStyles) { 667 //ctor 668 this.element = element; 669 670 this.text = this.element.textContent; 671 672 this.underlineStyles = underlineStyles; 673 674 this.elementStyles = elementStyles; 675 this.redrawActive = false; 676 677 this.canvas = document.createElement("canvas"); 678 this.ctx = this.canvas.getContext('2d'); 679 680 this.ratio = window.devicePixelRatio; 681 this.canvas.width = this.elementStyles.width*this.ratio; 682 this.canvas.height = this.elementStyles.height*this.ratio; 683 this.element.appendChild(this.canvas); 684 this.canvas.style.width = this.elementStyles.width + 'px'; 685 686 this.ctx.font = this.font = this.elementStyles.fontStyle + ' ' 687 + multiplyValue(this.elementStyles.fontSize, this.ratio) + ' ' 688 + this.elementStyles.fontFamily; 689 690 // determine the text-underline-width / strokeWidth 691 var dotWidth = this.ctx.measureText('.')['width']; 692 if (this.underlineStyles['text-underline-width'] == "auto") { 693 // if set to auto, calculate the optimized width based on font 694 this.strokeWidth = dotWidth/12; 695 } else { 696 //if set to px value, todo: other unit such as em? 697 this.strokeWidth = this.underlineStyles['text-underline-width']; 698 //get number value 699 this.strokeWidth = parseFloat(this.strokeWidth)*this.ratio; 700 } 701 702 703 // determine the text-underline-position / underlinePosition 704 // text-underline-position in ratio, todo: default and user set position ratio 705 if (this.underlineStyles['text-underline-position'] == "auto") { 706 // if set to auto, calculate the optimized width based on font 707 // console.log(this.elementStyles.baselinePositionRatio); 708 this.underlinePosition = parseFloat(this.elementStyles.height) * this.ratio 709 * ( 1 - this.elementStyles.baselinePositionRatio + 710 this.elementStyles.baselinePositionRatio * 0.4) 711 + this.strokeWidth/2; 712 } else { 713 //if set to ratio value, todo: other unit such as em, px? 714 var userUnderlinePosition = parseFloat(this.underlineStyles['text-underline-position']); 715 // console.log(this.elementStyles.baselinePositionRatio); 716 this.underlinePosition = parseFloat(this.elementStyles.height) * this.ratio * 717 ( 1 - this.elementStyles.baselinePositionRatio + 718 this.elementStyles.baselinePositionRatio * userUnderlinePosition) 719 + this.strokeWidth/2; 720 } 721 722 var adjustValue = optimalStrokeWidthPos(this.strokeWidth, this.underlinePosition); 723 this.strokeWidth = adjustValue.strokeWidth; 724 this.underlinePosition = adjustValue.posY; 725 726 // todo: if last character is a space, remove the space 727 textWidth = this.ctx.measureText(this.text).width; 728 729 this.myString = new GuitarString(this.ctx, 730 new Point(0, this.underlinePosition), 731 new Point(textWidth, this.underlinePosition), 732 this.strokeWidth, this.underlineStyles['text-underline-color'], this.ratio); 733 this.drawHoles(); 734 735} 736 737SingleUnderline.prototype.drawUnderline = function(){ 738 // draw the underline 739 this.myString.draw(); 740} 741 742SingleUnderline.prototype.drawHoles = function(){ 743 // draw the font stroke 744 this.ctx.font = this.font; 745 this.ctx.textBaseline = 'top'; 746 747 this.ctx.globalCompositeOperation = "destination-out"; 748 749 this.ctx.lineWidth = 2*this.ratio + this.strokeWidth*3.6; 750 this.ctx.strokeStyle = 'blue'; 751 this.ctx.beginPath(); 752 this.ctx.strokeText(this.text, -0.2, 0); 753 754 this.ctx.fillStyle = 'green'; 755 this.ctx.beginPath(); 756 this.ctx.fillText(this.text, -0.2, 0); 757} 758 759SingleUnderline.prototype.clear = function(){ 760 this.redrawActive = this.myString.redrawActive; 761 // clear 762 if(this.myString.redrawActive) { 763 // this.myString.clear(); 764 this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); 765 } 766}; 767 768 769SingleUnderline.prototype.update = function(){ 770 // update 771 if(this.myString.redrawActive) {
772 this.myString.update(); 773 } 774}; 775 776 777SingleUnderline.prototype.draw = function(){ 778 // draw 779 if(this.redrawActive) { 780 this.drawUnderline(); 781 this.drawHoles(); 782 } 783}; 784 785var getElementStyles = function(element){ 786 // lineHeight, height, ratio, fontFamily, fontSize, fontStyle 787 var $this = element; 788 789 var baselinePositionRatio = baselineRatio(element); 790 var lineHeight = parseFloat(window.getComputedStyle($this, null) 791 .getPropertyValue("line-height")); 792 var fontFamily = window.getComputedStyle($this, null) 793 .getPropertyValue("font-family"); 794 var fontSize = window.getComputedStyle($this, null) 795 .getPropertyValue("font-size"); 796 var fontStyle = window.getComputedStyle($this, null) 797 .getPropertyValue("font-style"); 798 var width = $this.getBoundingClientRect().width; 799 var height = $this.getBoundingClientRect().height; 800 var parentWidth = $this.parentNode.getBoundingClientRect().width; 801 802 803 var offsetLeft = $this.offsetLeft; 804 var parentOffsetLeft = $this.parentNode.offsetLeft; 805 var canvasLeft = parentOffsetLeft - offsetLeft; 806 var textIndent = offsetLeft - parentOffsetLeft; 807 808 // canvas.style.left= canvasLeft + 'px'; 809 return { 810 lineHeight: lineHeight, 811 width: width, 812 height: height, 813 parentWidth: parentWidth, 814 fontFamily: fontFamily, 815 fontSize: fontSize, 816 fontStyle: fontStyle, 817 baselinePositionRatio: baselinePositionRatio, 818 canvasLeft: canvasLeft, 819 textIndent: textIndent 820 } 821}; 822 823window.requestAnimFrame = (function(callback) { 824 return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || 825 function(callback) { 826 window.setTimeout(callback, 1000 / 60); 827 }; 828})(); 829 830var myUnderlines = []; 831var myMultipleUnderlines = []; 832window.onload = function() { 833 var underlineElements = document.querySelectorAll(".underline"); 834 for(var n = 0; n < underlineElements.length; n++) { 835 836 var element = underlineElements[n]; 837 838 var underlineStyles = { 839 'text-underline-color': '#000', 840 'text-underline-position': 'auto', // could be ratio or todo: px 841 'text-underline-skip': true, 842 'text-underline-width': 'auto' // could be auto or px or ratio 843 } 844 845 846 var elementStyles = getElementStyles(element); 847 // single line or multiple line? 848 if (elementStyles.height > elementStyles.lineHeight) { 849 // multiple lines 850 // console.log('multiple lines'); 851 var myUnderline = new MultipleUnderline(element, underlineStyles, elementStyles); 852 // myUnderline.update(); 853 // myUnderline.draw(); 854 myUnderlines.push(myUnderline); 855 } else { 856 // single line 857 var myUnderline = new SingleUnderline(element, underlineStyles, elementStyles); 858 myUnderlines.push(myUnderline); 859 } 860 861 // if(window.device) 862 } 863} 864 865 866function animate() { 867 868 for(var i = 0; i < myUnderlines.length; i++) { 869 var myUnderline = myUnderlines[i]; 870 871 // clear 872 myUnderline.clear(); 873 874 // update 875 myUnderline.update(); 876 877 // draw stuff 878 myUnderline.draw(); 879 } 880 881 882 // request new frame 883 requestAnimFrame(function() { 884 animate(); 885 }); 886} 887animate(); 888 889//audio play multiple channels at the same time: http://www.storiesinflight.com/html5/audio.html 890var channel_max = 10; // number of channels 891audiochannels = new Array();
892for (a = 0; a < channel_max; a++) { // prepare the channels 893 audiochannels[a] = new Array(); 894 audiochannels[a]['channel'] = new Audio(); // create a new audio object 895 audiochannels[a]['finished'] = -1; // expected end time for this channel 896} 897 898function play_multi_sound(s) { 899 for (a = 0; a < audiochannels.length; a++) { 900 thistime = new Date(); 901 if (audiochannels[a]['finished'] < thistime.getTime()) { // is this channel finished? 902 audiochannels[a]['finished'] = thistime.getTime() + document.getElementById(s).duration * 1000; 903 audiochannels[a]['channel'].src = document.getElementById(s).src; 904 audiochannels[a]['channel'].load(); 905 audiochannels[a]['channel'].play(); 906 break; 907 } 908 } 909} 910 911var is_chrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1; 912 913 914var circleCenter = function(startPoint, thirdPoint, endPoint){ 915 var dy1 = thirdPoint.y - startPoint.y; 916 var dx1 = thirdPoint.x - startPoint.x; 917 var dy2 = endPoint.y - thirdPoint.y; 918 var dx2 = endPoint.x - thirdPoint.x; 919 920 var aSlope = dy1/dx1; 921 var bSlope = dy2/dx2; 922 923 924 var centerX = (aSlope*bSlope*(startPoint.y - endPoint.y) + bSlope*(startPoint.x + thirdPoint.x) 925 - aSlope*(thirdPoint.x+endPoint.x) )/( 2* (bSlope-aSlope) ); 926 var centerY = -1*(centerX - (startPoint.x+thirdPoint.x)/2)/aSlope + (startPoint.y+thirdPoint.y)/2; 927 var r = dist(centerX, centerY, startPoint.x, startPoint.y) 928 929 return { 930 x: centerX, 931 y: centerY, 932 r: r 933 }; 934} 935 936var dist = function(x, y, x0, y0){ 937 return Math.sqrt((x -= x0) * x + (y -= y0) * y); 938}; 939 940var Point = function (x,y){ 941 this.x=x; 942 this.y=y; 943} 944 945 946var intersects = function(a, b, c, d, p, q, r, s) { 947 // returns true if the line from (a,b)->(c,d) intersects with (p,q)->(r,s) 948 var det, gamma, lambda; 949 det = (c - a) * (s - q) - (r - p) * (d - b); 950 if (det === 0) { 951 return false; 952 } else { 953 lambda = ((s - q) * (r - a) + (p - r) * (s - b)) / det; 954 gamma = ((b - d) * (r - a) + (c - a) * (s - b)) / det; 955 return (0 < lambda && lambda < 1) && (0 < gamma && gamma < 1); 956 } 957}; 958 959var musicLevel = function(startPoint, endPoint, ratio){ 960 var length = dist(startPoint.x, startPoint.y, endPoint.x, endPoint.y)/ratio; 961 962 level = Math.floor(length/30); 963 if (level > 19 ) { 964 level = 19; 965 } 966 level = 19 - level 967 if (level < 10) { 968 level = '0' + level 969 } 970 return level; 971}; 972function GuitarString(ctx, startPoint, endPoint, strokeWidth, strokeColor, ratio) { 973 //ctor 974 this.ctx = ctx; 975 this.canvas = ctx.canvas; 976 this.startPoint = startPoint; 977 this.endPoint = endPoint; 978 this.strokeWidth = strokeWidth; 979 this.strokeColor = strokeColor; 980 this.ratio = ratio; 981 982 this.level = musicLevel(this.startPoint, this.endPoint, this.ratio); 983 984 // this.canvas.width = this.canvas.clientWidth; 985 // this.canvas.height = this.canvas.clientHeight*1.2; 986 987 this.maxGrabDistance = this.strokeWidth * 2; 988 this.maxControlDistance = this.strokeWidth * 6; 989 990 this.ctx.lineWidth = this.strokeWidth; 991 this.ctx.strokeStyle = this.strokeColor; 992 this.ctx.beginPath(); 993 this.ctx.moveTo(this.startPoint.x, this.startPoint.y); 994 this.ctx.lineTo(this.endPoint.x, this.endPoint.y); 995 this.ctx.globalCompositeOperation = "source-over"; 996 this.ctx.stroke(); 997 998 this.currentMouseX; 999 this.currentMouseY; 1000 this.lastMouseX; 1001 this.lastMouseY; 1002 this.waveInitX = (this.startPoint.x + this.endPoint.x)/2; 1003 this.waveInitY = this.startPoint.y - this.maxControlDistance; 1004 this.waveCount = 0; 1005 this.damping = 0.9; 1006 1007 this.thirdPoint = new Point((this.startPoint.x + this.endPoint.x)/2, this.startPoint.y); 1008 1009 1010 // state flags 1011 this.userInControl = false; 1012 this.userPlucked = false; 1013 this.waveInControl = false; 1014 this.waveFinished = false; 1015 this.initState = true; 1016 this.lastRedraw = false; 1017 this.redrawActive = false; 1018 1019 //add event listener 1020 var self = this; 1021 this.canvas.addEventListener('mouseover', function(event) { 1022 self.mouseOver(self, event); 1023 }, false); 1024 1025 this.canvas.addEventListener('mousemove', function(event) { 1026 self.mouseMove(self, event); 1027 }, false); 1028 1029 this.canvas.addEventListener ("mouseleave", function(event){ 1030 self.mouseLeave(self, event); 1031 }, false); 1032 1033 this.canvas.addEventListener ("mouseout", function(event){ 1034 self.mouseOut(self, event); 1035 }, false); 1036 1037 this.canvas.addEventListener("touchstart", function(event) { 1038 self.touchDown(self, event); 1039 }, false); 1040 this.canvas.addEventListener("touchmove", function(event) { 1041 self.touchXY(self, event); 1042 }, false); 1043 this.canvas.addEventListener("touchend", function(event) { 1044 self.touchUp(self, event); 1045 }, false); 1046 1047} 1048 1049GuitarString.prototype.mouseOver = function(self, event){ 1050 // console.log('mouseOver'); 1051 this.currentMouseX = event.layerX*this.ratio; 1052 this.currentMouseX = event.layerY*this.ratio; 1053}; 1054GuitarString.prototype.mouseMove = function (self, event){ 1055 // console.log('mouseMove'); 1056 this.lastMouseX = this.currentMouseX; 1057 this.lastMouseY = this.currentMouseY; 1058 this.currentMouseX = event.layerX * this.ratio; 1059 this.currentMouseY = event.layerY * this.ratio; 1060 1061 var radius = circleCenter( new Point(this.startPoint.x, this.startPoint.y), 1062 new Point(this.currentMouseX, this.currentMouseY), 1063 new Point(this.endPoint.x, this.endPoint.y) ).r; 1064 var currentWaveDistance = radius - Math.sqrt( Math.pow(radius, 2) - Math.pow((Math.abs(this.endPoint.x - this.startPoint.x))/2, 2) ); 1065 var lastRadius = circleCenter( new Point(this.startPoint.x, this.startPoint.y), 1066 new Point(this.lastMouseX, this.lastMouseY), 1067 new Point(this.endPoint.x, this.endPoint.y) ).r; 1068 var lastWaveDistance = lastRadius - Math.sqrt( Math.pow(lastRadius, 2) 1069 - Math.pow((Math.abs(this.endPoint.x - this.startPoint.x))/2, 2) ); 1070 1071 1072 1073 var mouseInGrabRange = currentWaveDistance < this.maxGrabDistance 1074 && this.currentMouseX > this.startPoint.x 1075 && this.currentMouseX < this.endPoint.x; 1076 1077 var lastMouseOutGrabRange = !(lastWaveDistance < this.maxGrabDistance 1078 && this.lastMouseX > this.startPoint.x 1079 && this.lastMouseX < this.endPoint.x);
1080 1081 var mouseOutControlRange = !(currentWaveDistance < this.maxControlDistance 1082 && this.currentMouseX > this.startPoint.x 1083 && this.currentMouseX < this.endPoint.x); 1084 1085 var lastMouseInControlRange = lastWaveDistance < this.maxControlDistance 1086 && this.lastMouseX > this.startPoint.x 1087 && this.lastMouseY < this.endPoint.x; 1088 1089 var mouseCrossed = intersects(this.lastMouseX, this.lastMouseY, 1090 this.currentMouseX, this.currentMouseY, 1091 this.startPoint.x, this.startPoint.y, 1092 this.endPoint.x, this.endPoint.y); 1093 1094 if( mouseInGrabRange && lastMouseOutGrabRange && (!this.userInControl) ){ 1095 // console.log('grab!'); 1096 this.initState = false; 1097 this.userInControl = true; 1098 this.waveInControl = false; 1099 this.waveFinished = false; 1100 1101 this.redrawActive = true; 1102 } else if ( mouseOutControlRange && lastMouseInControlRange && this.userInControl){ 1103 // console.log('boing!'); 1104 this.initState = false; 1105 this.userInControl = false; 1106 this.waveInControl = true; 1107 this.waveFinished = false; 1108 this.waveCount = 0; 1109 // this.waveInitX = this.lastMouseX; 1110 // this.waveInitY = this.lastMouseY; 1111 this.waveInitX = (this.startPoint.x + this.endPoint.x)/2; 1112 this.waveInitY = this.endPoint.y + this.maxControlDistance; 1113 // play audio 1114 play_multi_sound('audio' + this.level); 1115 // createjs.Sound.play('cello_' + this.level); 1116 1117 } 1118 1119 if( (!this.userInControl)&&mouseCrossed ) { 1120 // console.log('i just plucked!'); 1121 this.initState = false; 1122 this.userInControl = false; 1123 this.waveInControl = true; 1124 this.waveFinished = false; 1125 this.redrawActive = true; 1126 this.waveCount = 0; 1127 this.waveInitX = (this.startPoint.x + this.endPoint.y)/2; 1128 this.waveInitY = this.endPoint.y + this.maxGrabDistance * 2 / 3; 1129 } 1130}; 1131GuitarString.prototype.mouseLeave = function(self, event){ 1132 // console.log('mouseLeave'); 1133 if( this.userInControl ) { 1134 this.initState = false; 1135 this.userInControl = false; 1136 this.waveInControl = true; 1137 this.waveFinished = false; 1138 this.redrawActive = true; 1139 this.waveCount = 0; 1140 1141 this.waveInitX = event.layerX*this.ratio; 1142 this.waveInitY = event.layerY*this.ratio; 1143 1144 } 1145}; 1146GuitarString.prototype.mouseOut = function(self, event){ 1147 // console.log('mouseOut'); 1148}; 1149GuitarString.prototype.touchDown = function(self, event){ 1150 // console.log('touchDown'); 1151 this.currentMouseX = event.layerX*this.ratio; 1152 this.currentMouseX = event.layerY*this.ratio; 1153}; 1154GuitarString.prototype.touchXY = function (self, event){ 1155 // console.log('touchMove'); 1156 this.lastMouseX = this.currentMouseX; 1157 this.lastMouseY = this.currentMouseY; 1158 this.currentMouseX = event.layerX * this.ratio; 1159 this.currentMouseY = event.layerY * this.ratio; 1160 1161 var radius = circleCenter( new Point(this.startPoint.x, this.startPoint.y), 1162 new Point(this.currentMouseX, this.currentMouseY), 1163 new Point(this.endPoint.x, this.endPoint.y) ).r; 1164 var currentWaveDistance = radius - Math.sqrt( Math.pow(radius, 2) - Math.pow((Math.abs(this.endPoint.x - this.startPoint.x))/2, 2) ); 1165 var lastRadius = circleCenter( new Point(this.startPoint.x, this.startPoint.y), 1166 new Point(this.lastMouseX, this.lastMouseY), 1167 new Point(this.endPoint.x, this.endPoint.y) ).r; 1168 var lastWaveDistance = lastRadius - Math.sqrt( Math.pow(lastRadius, 2) 1169 - Math.pow((Math.abs(this.endPoint.x - this.startPoint.x))/2, 2) ); 1170 1171 1172 1173 var mouseInGrabRange = currentWaveDistance < this.maxGrabDistance 1174 && this.currentMouseX > this.startPoint.x 1175 && this.currentMouseX < this.endPoint.x; 1176 1177 var lastMouseOutGrabRange = !(lastWaveDistance < this.maxGrabDistance 1178 && this.lastMouseX > this.startPoint.x 1179 && this.lastMouseX < this.endPoint.x);
1180 1181 var mouseOutControlRange = !(currentWaveDistance < this.maxControlDistance 1182 && this.currentMouseX > this.startPoint.x 1183 && this.currentMouseX < this.endPoint.x); 1184 1185 var lastMouseInControlRange = lastWaveDistance < this.maxControlDistance 1186 && this.lastMouseX > this.startPoint.x 1187 && this.lastMouseY < this.endPoint.x; 1188 1189 var mouseCrossed = intersects(this.lastMouseX, this.lastMouseY, 1190 this.currentMouseX, this.currentMouseY, 1191 this.startPoint.x, this.startPoint.y, 1192 this.endPoint.x, this.endPoint.y); 1193 1194 if( mouseInGrabRange && lastMouseOutGrabRange && (!this.userInControl) ){ 1195 // console.log('grab!'); 1196 this.initState = false; 1197 this.userInControl = true; 1198 this.waveInControl = false; 1199 this.waveFinished = false; 1200 1201 this.redrawActive = true; 1202 } else if ( mouseOutControlRange && lastMouseInControlRange && this.userInControl){ 1203 // console.log('boing!'); 1204 this.initState = false; 1205 this.userInControl = false; 1206 this.waveInControl = true; 1207 this.waveFinished = false; 1208 this.waveCount = 0; 1209 // this.waveInitX = this.lastMouseX; 1210 // this.waveInitY = this.lastMouseY; 1211 this.waveInitX = (this.startPoint.x + this.endPoint.x)/2; 1212 this.waveInitY = this.endPoint.y + this.maxControlDistance; 1213 // play audio 1214 play_multi_sound('audio' + this.level); 1215 1216 } 1217 1218 if( (!this.userInControl)&&mouseCrossed ) { 1219 // console.log('i just plucked!'); 1220 this.initState = false; 1221 this.userInControl = false; 1222 this.waveInControl = true; 1223 this.waveFinished = false; 1224 this.redrawActive = true; 1225 this.waveCount = 0; 1226 this.waveInitX = (this.startPoint.x + this.endPoint.y)/2; 1227 this.waveInitY = this.endPoint.y + this.maxGrabDistance * 2 / 3; 1228 // play audio 1229 // play_multi_sound('audio' + this.level); 1230 1231 } 1232}; 1233GuitarString.prototype.touchUp = function(self, event){ 1234 // console.log('touchUp'); 1235 if( this.userInControl ) { 1236 this.initState = false; 1237 this.userInControl = false; 1238 this.waveInControl = true; 1239 this.waveFinished = false; 1240 this.redrawActive = true; 1241 this.waveCount = 0; 1242 1243 this.waveInitX = event.layerX*this.ratio; 1244 this.waveInitY = event.layerY*this.ratio; 1245 1246 } 1247}; 1248 1249 1250GuitarString.prototype.clear = function(){ 1251 // clear 1252 // if(this.redrawActive){ 1253 // this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); 1254 // } 1255}; 1256 1257GuitarString.prototype.update = function(){ 1258 // console.log(this.redrawActive); 1259 // if(this.redrawActive){ 1260 if ( this.userInControl ){ 1261 this.thirdPoint = new Point(this.currentMouseX, this.currentMouseY); 1262 } 1263 if ( this.waveInControl ){ 1264 var waveX = this.waveInitX; 1265 var waveY = this.startPoint.y + 1266 (this.waveInitY-this.startPoint.y) 1267 *Math.cos(this.waveCount/5*Math.PI) 1268 *Math.pow(this.damping, this.waveCount); 1269 1270 if ( Math.pow(this.damping, this.waveCount) > 0.03) { 1271 // still waving .... 1272 this.thirdPoint = new Point(waveX, waveY); 1273 this.waveCount++; 1274 } else { 1275 // wave damped to a straight line, wave is finished 1276 this.waveInControl = false; 1277 this.waveFinished = true; 1278 this.lastRedraw = true; 1279 this.thirdPoint = new Point(waveX, waveY); 1280 } 1281 } 1282 // } 1283} 1284 1285GuitarString.prototype.draw = function(){ 1286 // if(this.redrawActive){ 1287 // draw stuff 1288 // } 1289 if(this.lastRedraw) { 1290 this.drawLine(); 1291 this.lastRedraw = false; 1292 this.redrawActive = false; 1293 } else { 1294 this.drawArc(this.startPoint, this.thirdPoint, this.endPoint); 1295 } 1296}; 1297GuitarString.prototype.drawLine = function(){ 1298 // draw a line instead of a flat curve when it stops redraw, pixel-perfect 1299 this.ctx.lineWidth = this.strokeWidth; 1300 this.ctx.strokeStyle = this.strokeColor; 1301 this.ctx.beginPath(); 1302 this.ctx.moveTo(this.startPoint.x, this.startPoint.y); 1303 this.ctx.lineTo(this.endPoint.x, this.endPoint.y); 1304 this.ctx.globalCompositeOperation = "source-over"; 1305 this.ctx.stroke(); 1306}; 1307 1308 1309GuitarString.prototype.drawArc = function(startPoint, thirdPoint, endPoint){ 1310 var ctx = this.ctx; 1311 ctx.lineWidth = this.strokeWidth; 1312 ctx.strokeStyle = this.strokeColor; 1313 1314 var centerObject = circleCenter( new Point(startPoint.x, startPoint.y), 1315 new Point(thirdPoint.x, thirdPoint.y), 1316 new Point(endPoint.x, endPoint.y) ); 1317 var centerX = centerObject.x; 1318 var centerY = centerObject.y; 1319 var r = centerObject.r 1320 1321 var angle = Math.atan2(centerX-startPoint.x, centerY-startPoint.y); 1322 // console.log(centerObject); 1323 if (!angle){ 1324 ctx.beginPath(); 1325 ctx.moveTo(startPoint.x, startPoint.y); 1326 ctx.lineTo(endPoint.x, endPoint.y); 1327 } else { 1328 if( angle > Math.PI/2) { 1329 ctx.beginPath(); 1330 ctx.arc(centerX, centerY, r, Math.PI * 1.5-angle, Math.PI * 1.5 + angle, true); 1331 } else { 1332 ctx.beginPath(); 1333 ctx.arc(centerX, centerY, r, Math.PI * 1.5-angle, Math.PI * 1.5 + angle, false); 1334 } 1335 } 1336 ctx.globalCompositeOperation = "source-over"; 1337 ctx.stroke(); 1338 1339} 1340
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.