mirror of
https://github.com/scratchfoundation/scratch-render.git
synced 2025-08-28 22:30:04 -04:00
Remove speech bubbles' reliance on svg quirks mode's _transformText
This commit is contained in:
parent
ae16a64e78
commit
528ae873d7
3 changed files with 56 additions and 24 deletions
|
@ -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);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
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;
|
||||
|
@ -8,10 +7,23 @@ const MIN_WIDTH = 50;
|
|||
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}
|
||||
|
@ -133,7 +145,7 @@ 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);
|
||||
}
|
||||
|
@ -141,6 +153,7 @@ class SVGTextBubble {
|
|||
}
|
||||
|
||||
_wrapSvgFragment (fragment) {
|
||||
// @todo generate view box
|
||||
return `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
${fragment}
|
||||
|
@ -148,14 +161,12 @@ class SVGTextBubble {
|
|||
`;
|
||||
}
|
||||
|
||||
_textFragment () {
|
||||
return `<text fill="#575E75">${xmlescape(this.lines.join('\n'))}</text>`;
|
||||
}
|
||||
|
||||
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 = '';
|
||||
|
||||
|
@ -169,7 +180,7 @@ class SVGTextBubble {
|
|||
} else {
|
||||
fragment += this._thinkBubble(fullWidth, fullHeight, radius, this.pointsLeft);
|
||||
}
|
||||
fragment += `<g transform="translate(${padding - x}, ${padding - y})">${this._textFragment()}</g>`;
|
||||
fragment += `<g transform="translate(${padding - x}, ${padding - y})">${this._textFragment}</g>`;
|
||||
return this._wrapSvgFragment(fragment);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue