diff --git a/core/field.js b/core/field.js index e089adc3b..27c0b6220 100644 --- a/core/field.js +++ b/core/field.js @@ -257,6 +257,18 @@ Blockly.Field.prototype.EDITABLE = true; */ Blockly.Field.prototype.SERIALIZABLE = false; +/** + * Point size of text. Should match blocklyText's font-size in CSS. + * @const {string} + */ +Blockly.Field.FONTSIZE = 11; + +/** + * Text font family. Should match blocklyText's font-family in CSS. + * @const {string} + */ +Blockly.Field.FONTFAMILY = 'sans-serif'; + /** * Process the configuration map passed to the field. * @param {!Object} config A map of options used to configure the field. See @@ -641,8 +653,9 @@ Blockly.Field.prototype.updateWidth = function() { * @protected */ Blockly.Field.prototype.updateSize_ = function() { - var textWidth = Blockly.utils.dom.getTextWidth( - /** @type {!SVGTextElement} */ (this.textElement_)); + var textWidth = Blockly.utils.dom.getFastTextWidth( + /** @type {!SVGTextElement} */ (this.textElement_), + Blockly.Field.FONTSIZE, Blockly.Field.FONTFAMILY); var totalWidth = textWidth; if (this.borderRect_) { totalWidth += Blockly.Field.X_PADDING; diff --git a/core/field_dropdown.js b/core/field_dropdown.js index 3f38b3a6d..3a67f6be4 100644 --- a/core/field_dropdown.js +++ b/core/field_dropdown.js @@ -514,8 +514,9 @@ Blockly.FieldDropdown.prototype.renderSelectedImage_ = function(imageJson) { this.imageElement_.setAttribute('height', imageJson.height); this.imageElement_.setAttribute('width', imageJson.width); - var arrowWidth = Blockly.utils.dom.getTextWidth( - /** @type {!SVGTSpanElement} */ (this.arrow_)); + var arrowWidth = Blockly.utils.dom.getFastTextWidth( + /** @type {!SVGTSpanElement} */ (this.arrow_), + Blockly.Field.FONTSIZE, Blockly.Field.FONTFAMILY); var imageHeight = Number(imageJson.height); var imageWidth = Number(imageJson.width); @@ -549,7 +550,8 @@ Blockly.FieldDropdown.prototype.renderSelectedText_ = function() { this.textElement_.setAttribute('x', Blockly.Field.DEFAULT_TEXT_OFFSET); // Height and width include the border rect. this.size_.height = Blockly.Field.BORDER_RECT_DEFAULT_HEIGHT; - this.size_.width = Blockly.utils.dom.getTextWidth(this.textElement_) + + this.size_.width = Blockly.utils.dom.getFastTextWidth(this.textElement_, + Blockly.Field.FONTSIZE, Blockly.Field.FONTFAMILY) + Blockly.Field.X_PADDING; }; diff --git a/core/field_multilineinput.js b/core/field_multilineinput.js index 26ba0a34c..9ecf70bf5 100644 --- a/core/field_multilineinput.js +++ b/core/field_multilineinput.js @@ -245,7 +245,7 @@ Blockly.FieldMultilineInput.prototype.widgetCreate_ = function() { var htmlInput = /** @type {HTMLTextAreaElement} */ (document.createElement('textarea')); htmlInput.className = 'blocklyHtmlInput blocklyHtmlTextAreaInput'; htmlInput.setAttribute('spellcheck', this.spellcheck_); - var fontSize = (Blockly.FieldTextInput.FONTSIZE * scale) + 'pt'; + var fontSize = (Blockly.Field.FONTSIZE * scale) + 'pt'; div.style.fontSize = fontSize; htmlInput.style.fontSize = fontSize; var borderRadius = (Blockly.FieldTextInput.BORDERRADIUS * scale) + 'px'; diff --git a/core/field_textinput.js b/core/field_textinput.js index 004348309..631923a71 100644 --- a/core/field_textinput.js +++ b/core/field_textinput.js @@ -107,11 +107,6 @@ Blockly.FieldTextInput.fromJson = function(options) { */ Blockly.FieldTextInput.prototype.SERIALIZABLE = true; -/** - * Point size of text. Should match blocklyText's font-size in CSS. - */ -Blockly.FieldTextInput.FONTSIZE = 11; - /** * Pixel size of input border radius. * Should match blocklyText's border-radius in CSS. @@ -291,7 +286,7 @@ Blockly.FieldTextInput.prototype.widgetCreate_ = function() { htmlInput.className = 'blocklyHtmlInput'; htmlInput.setAttribute('spellcheck', this.spellcheck_); var fontSize = - (Blockly.FieldTextInput.FONTSIZE * this.workspace_.scale) + 'pt'; + (Blockly.Field.FONTSIZE * this.workspace_.scale) + 'pt'; div.style.fontSize = fontSize; htmlInput.style.fontSize = fontSize; var borderRadius = diff --git a/core/utils/dom.js b/core/utils/dom.js index 045cc17ad..dfacc4188 100644 --- a/core/utils/dom.js +++ b/core/utils/dom.js @@ -76,6 +76,13 @@ Blockly.utils.dom.cacheWidths_ = null; */ Blockly.utils.dom.cacheReference_ = 0; +/** + * A HTML canvas context used for computing text width. + * @type {CanvasRenderingContext2D} + * @private + */ +Blockly.utils.dom.canvasContext_ = null; + /** * Helper method for creating SVG elements. * @param {string} name Element's tag name. @@ -271,3 +278,50 @@ Blockly.utils.dom.getTextWidth = function(textElement) { } return width; }; + +/** + * Gets the width of a text element using a faster method than `getTextWidth`. + * This method requires that we know the text element's font family and size in + * advance. Similar to `getTextWidth`, we cache the width we compute. + * @param {!Element} textElement An SVG 'text' element. + * @param {string} fontSize The font size to use. + * @param {string} fontFamily The font family to use. + * @return {number} Width of element. + */ +Blockly.utils.dom.getFastTextWidth = function(textElement, + fontSize, fontFamily) { + var text = textElement.textContent; + var key = text + '\n' + textElement.className.baseVal; + var width; + + // Return the cached width if it exists. + if (Blockly.utils.dom.cacheWidths_) { + width = Blockly.utils.dom.cacheWidths_[key]; + if (width) { + return width; + } + } + + if (!Blockly.utils.dom.canvasContext_) { + // Inject the canvas element used for computing text widths. + var computeCanvas = document.createElement('canvas'); + computeCanvas.className = 'blocklyComputeCanvas'; + document.body.appendChild(computeCanvas); + + // Initialize the HTML canvas context and set the font. + // The context font must match blocklyText's fontsize and font-family + // set in CSS. + Blockly.utils.dom.canvasContext_ = computeCanvas.getContext('2d'); + } + // Set the desired font size and family. + Blockly.utils.dom.canvasContext_.font = fontSize + 'pt ' + fontFamily; + + // Measure the text width using the helper canvas context. + width = Blockly.utils.dom.canvasContext_.measureText(text).width; + + // Cache the computed width and return. + if (Blockly.utils.dom.cacheWidths_) { + Blockly.utils.dom.cacheWidths_[key] = width; + } + return width; +};