Merge pull request #286 from fsih/removeRenderQuirks

Remove speech bubbles' reliance on svg quirks mode's _transformText
This commit is contained in:
DD Liu
2018-05-21 14:53:21 -04:00
committed by GitHub
3 changed files with 71 additions and 35 deletions

View File

@@ -59,11 +59,11 @@ class Silhouette {
const height = this._height = canvas.height = bitmapData.height;
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, width, height);
ctx.drawImage(bitmapData, 0, 0, width, height);
if (!(width && height)) {
return;
}
ctx.clearRect(0, 0, width, height);
ctx.drawImage(bitmapData, 0, 0, width, height);
const imageData = ctx.getImageData(0, 0, width, height);
this._data = new Uint8ClampedArray(imageData.data.length / 4);

View File

@@ -1,17 +1,30 @@
const SVGTextWrapper = require('./svg-text-wrapper');
const SvgRenderer = require('scratch-svg-renderer').SVGRenderer;
const xmlescape = require('xml-escape');
const MAX_LINE_LENGTH = 170;
const MIN_WIDTH = 50;
const STROKE_WIDTH = 4;
class SVGTextBubble {
constructor () {
this.svgRenderer = new SvgRenderer();
this.svgTextWrapper = new SVGTextWrapper();
this.svgTextWrapper = new SVGTextWrapper(this.makeSvgTextElement);
this._textSizeCache = {};
}
/**
* @return {SVGElement} an SVG text node with the properties that we want for speech bubbles.
*/
makeSvgTextElement () {
const svgText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
svgText.setAttribute('alignment-baseline', 'text-before-edge');
svgText.setAttribute('font-size', '14');
svgText.setAttribute('fill', '#575E75');
// TODO Do we want to use the new default sans font instead of Helvetica?
svgText.setAttribute('font-family', 'Helvetica');
return svgText;
}
_speechBubble (w, h, radius, pointsLeft) {
let pathString = `
M 0 ${radius}
@@ -45,7 +58,7 @@ class SVGTextBubble {
<path
d="${pathString}"
stroke="rgba(0, 0, 0, 0.15)"
stroke-width="4"
stroke-width="${STROKE_WIDTH}"
fill="rgba(0, 0, 0, 0.15)"
stroke-line-join="round"
/>
@@ -101,7 +114,7 @@ class SVGTextBubble {
rx="${rx}" ry="${ry}"
fill="rgba(0, 0, 0, 0.15)"
stroke="rgba(0, 0, 0, 0.15)"
stroke-width="4"
stroke-width="${STROKE_WIDTH}"
/>
<ellipse
cx="${cx}" cy="${cy}"
@@ -125,7 +138,8 @@ class SVGTextBubble {
return `
<g>
<path d="${pathString}" stroke="rgba(0, 0, 0, 0.15)" stroke-width="4" fill="rgba(0, 0, 0, 0.15)" />
<path d="${pathString}" stroke="rgba(0, 0, 0, 0.15)" stroke-width="${STROKE_WIDTH}"
fill="rgba(0, 0, 0, 0.15)" />
<path d="${pathString}" stroke="none" fill="white" />
${ellipses.join('\n')}
</g>`;
@@ -133,30 +147,31 @@ class SVGTextBubble {
_getTextSize () {
const svgString = this._wrapSvgFragment(this._textFragment());
const svgString = this._wrapSvgFragment(this._textFragment);
if (!this._textSizeCache[svgString]) {
this._textSizeCache[svgString] = this.svgRenderer.measure(svgString);
}
return this._textSizeCache[svgString];
}
_wrapSvgFragment (fragment) {
return `
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
${fragment}
</svg>
`;
}
_textFragment () {
const attrs = `font-family="Helvetica, Arial, sans-serif" font-size="14px" fill="#575E75"`;
return `<text ${attrs}>${xmlescape(this.lines.join('\n'))}</text>`;
_wrapSvgFragment (fragment, width, height) {
let svgString = `<svg xmlns="http://www.w3.org/2000/svg" version="1.1"`;
if (width && height) {
svgString = `${svgString} viewBox="
${-STROKE_WIDTH / 2} ${-STROKE_WIDTH / 2} ${width + STROKE_WIDTH} ${height + STROKE_WIDTH + 12}">`;
} else {
svgString = `${svgString}>`;
}
svgString = `${svgString} ${fragment} </svg>`;
return svgString;
}
buildString (type, text, pointsLeft) {
this.type = type;
this.pointsLeft = pointsLeft;
this.lines = this.svgTextWrapper.wrapText(MAX_LINE_LENGTH, text);
const textNode = this.svgTextWrapper.wrapText(MAX_LINE_LENGTH, text);
const serializer = new XMLSerializer();
this._textFragment = serializer.serializeToString(textNode);
let fragment = '';
@@ -170,8 +185,8 @@ class SVGTextBubble {
} else {
fragment += this._thinkBubble(fullWidth, fullHeight, radius, this.pointsLeft);
}
fragment += `<g transform="translate(${padding - x}, ${padding - y})">${this._textFragment()}</g>`;
return this._wrapSvgFragment(fragment);
fragment += `<g transform="translate(${padding - x}, ${padding - y})">${this._textFragment}</g>`;
return this._wrapSvgFragment(fragment, fullWidth, fullHeight);
}
}

View File

@@ -1,13 +1,19 @@
const TextWrapper = require('./text-wrapper');
const xmlescape = require('xml-escape');
/**
* Measure text by using a hidden SVG attached to the DOM.
* For use with TextWrapper.
*/
class SVGMeasurementProvider {
constructor () {
/**
* @param {function} makeTextElement - provides a text node of an SVGElement
* with the style of the text to be wrapped.
*/
constructor (makeTextElement) {
this._svgRoot = null;
this._cache = {};
this.makeTextElement = makeTextElement;
}
/**
@@ -61,16 +67,7 @@ class SVGMeasurementProvider {
const svgRoot = document.createElementNS(svgNamespace, 'svg');
const svgGroup = document.createElementNS(svgNamespace, 'g');
const svgText = document.createElementNS(svgNamespace, 'text');
// Normalize text attributes to match what the svg-renderer does.
// @TODO This code should be shared with the svg-renderer.
svgText.setAttribute('alignment-baseline', 'text-before-edge');
svgText.setAttribute('font-size', '14');
// TODO Do we want to use the new default sans font instead of Helvetica?
// This change intentionally subverts the svg-renderer auto font conversion.
svgText.setAttribute('font-family', 'Helvetica, Arial, sans-serif');
const svgText = this.makeTextElement();
// hide from the user, including screen readers
svgRoot.setAttribute('style', 'position:absolute;visibility:hidden');
@@ -99,8 +96,32 @@ class SVGMeasurementProvider {
* TextWrapper specialized for SVG text.
*/
class SVGTextWrapper extends TextWrapper {
constructor () {
super(new SVGMeasurementProvider());
/**
* @param {function} makeTextElement - provides a text node of an SVGElement
* with the style of the text to be wrapped.
*/
constructor (makeTextElement) {
super(new SVGMeasurementProvider(makeTextElement));
this.makeTextElement = makeTextElement;
}
/**
* Wrap the provided text into lines restricted to a maximum width. See Unicode Standard Annex (UAX) #14.
* @param {number} maxWidth - the maximum allowed width of a line.
* @param {string} text - the text to be wrapped. Will be split on whitespace.
* @returns {SVGElement} wrapped text node
*/
wrapText (maxWidth, text) {
const lines = super.wrapText(maxWidth, text);
const textElement = this.makeTextElement();
for (const line of lines) {
const tspanNode = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
tspanNode.setAttribute('x', '0');
tspanNode.setAttribute('dy', '1.2em');
tspanNode.textContent = xmlescape(line);
textElement.appendChild(tspanNode);
}
return textElement;
}
}