Improve consistency of SVGs generated, and fix non-BMP unicode exports from VirtualDocument

This commit is contained in:
David Evans 2018-04-22 19:17:31 +01:00
parent 2a7d9e76ed
commit 816206ed33
13 changed files with 117 additions and 34 deletions

View File

@ -7,7 +7,7 @@
script-src 'self' https://cdnjs.cloudflare.com https://unpkg.com;
style-src 'self'
https://cdnjs.cloudflare.com
'sha256-ru2GY2rXeOf7PQX5LzK3ckNo21FCDUoRc2f3i0QcD1g='
'sha256-s7UPtBgvov5WNF9C1DlTZDpqwLgEmfiWha5a5p/Zn7E='
;
font-src 'self' data:;
img-src 'self' blob:;

View File

@ -7,7 +7,7 @@
script-src 'self' https://cdnjs.cloudflare.com https://unpkg.com;
style-src 'self'
https://cdnjs.cloudflare.com
'sha256-ru2GY2rXeOf7PQX5LzK3ckNo21FCDUoRc2f3i0QcD1g='
'sha256-s7UPtBgvov5WNF9C1DlTZDpqwLgEmfiWha5a5p/Zn7E='
;
font-src 'self' data:;
img-src 'self' blob:;

View File

@ -4022,6 +4022,14 @@
return attrs;
}
function shrinkWhitespace(text) {
return text.replace(/[\f\n\r\t\v ]+/g, ' ');
}
function trimCollapsible(text) {
return text.replace(/^[\f\n\r\t\v ]+|[\f\n\r\t\v ]+$/g, '');
}
function parseMarkdown(text) {
if(!text) {
return [];
@ -4030,13 +4038,14 @@
const active = STYLES.map(() => false);
let activeCount = 0;
let attrs = null;
const lines = text.split('\n');
const lines = trimCollapsible(text).split('\n');
const result = [];
lines.forEach((line) => {
const ln = shrinkWhitespace(trimCollapsible(line));
const parts = [];
let p = 0;
for(;;) {
const {styleIndex, start, end} = findNext(line, p, active);
const {styleIndex, start, end} = findNext(ln, p, active);
if(styleIndex === -1) {
break;
}
@ -4048,13 +4057,13 @@
++ activeCount;
}
if(start > p) {
parts.push({attrs, text: line.substring(p, start)});
parts.push({attrs, text: ln.substring(p, start)});
}
attrs = combineAttrs(activeCount, active);
p = end;
}
if(p < line.length) {
parts.push({attrs, text: line.substr(p)});
if(p < ln.length) {
parts.push({attrs, text: ln.substr(p)});
}
result.push(parts);
});
@ -8456,7 +8465,7 @@
/* eslint-disable max-lines */
const FONT$3 = Handlee.name;
const FONT_FAMILY = '\'' + FONT$3 + '\',cursive';
const FONT_FAMILY = FONT$3 + ',cursive';
const LINE_HEIGHT$3 = 1.5;
const MAX_CHAOS = 5;
@ -8824,7 +8833,7 @@
// Font must be embedded for exporting as SVG / PNG
style.text(
'@font-face{' +
'font-family:"' + Handlee.name + '";' +
'font-family:' + Handlee.name + ';' +
'src:url("data:font/woff2;base64,' + Handlee.woff2 + '");' +
'}'
);

File diff suppressed because one or more lines are too long

View File

@ -4022,6 +4022,14 @@
return attrs;
}
function shrinkWhitespace(text) {
return text.replace(/[\f\n\r\t\v ]+/g, ' ');
}
function trimCollapsible(text) {
return text.replace(/^[\f\n\r\t\v ]+|[\f\n\r\t\v ]+$/g, '');
}
function parseMarkdown(text) {
if(!text) {
return [];
@ -4030,13 +4038,14 @@
const active = STYLES.map(() => false);
let activeCount = 0;
let attrs = null;
const lines = text.split('\n');
const lines = trimCollapsible(text).split('\n');
const result = [];
lines.forEach((line) => {
const ln = shrinkWhitespace(trimCollapsible(line));
const parts = [];
let p = 0;
for(;;) {
const {styleIndex, start, end} = findNext(line, p, active);
const {styleIndex, start, end} = findNext(ln, p, active);
if(styleIndex === -1) {
break;
}
@ -4048,13 +4057,13 @@
++ activeCount;
}
if(start > p) {
parts.push({attrs, text: line.substring(p, start)});
parts.push({attrs, text: ln.substring(p, start)});
}
attrs = combineAttrs(activeCount, active);
p = end;
}
if(p < line.length) {
parts.push({attrs, text: line.substr(p)});
if(p < ln.length) {
parts.push({attrs, text: ln.substr(p)});
}
result.push(parts);
});
@ -8456,7 +8465,7 @@
/* eslint-disable max-lines */
const FONT$3 = Handlee.name;
const FONT_FAMILY = '\'' + FONT$3 + '\',cursive';
const FONT_FAMILY = FONT$3 + ',cursive';
const LINE_HEIGHT$3 = 1.5;
const MAX_CHAOS = 5;
@ -8824,7 +8833,7 @@
// Font must be embedded for exporting as SVG / PNG
style.text(
'@font-face{' +
'font-family:"' + Handlee.name + '";' +
'font-family:' + Handlee.name + ';' +
'src:url("data:font/woff2;base64,' + Handlee.woff2 + '");' +
'}'
);
@ -9981,15 +9990,21 @@
});
function encodeChar(c) {
return '&#' + c.charCodeAt(0).toString(10) + ';';
return '&#' + c.codePointAt(0).toString(10) + ';';
}
function escapeHTML(text) {
return text.replace(/[^\r\n\t -%'-;=?-~]/g, encodeChar);
return text.replace(
/[\uD800-\uDBFF][\uDC00-\uDFFF]|[^\r\n\t -%'-;=?-~]/g,
encodeChar
);
}
function escapeQuoted(text) {
return text.replace(/[^\r\n\t !#$%(-;=?-~]/g, encodeChar);
return text.replace(
/[\uD800-\uDBFF][\uDC00-\uDFFF]|[^\r\n\t !#$%'-;=?-~]/g,
encodeChar
);
}
class TextNode {

View File

@ -8,7 +8,7 @@
style-src 'self'
https://cdnjs.cloudflare.com
https://fonts.googleapis.com
'sha256-ru2GY2rXeOf7PQX5LzK3ckNo21FCDUoRc2f3i0QcD1g='
'sha256-s7UPtBgvov5WNF9C1DlTZDpqwLgEmfiWha5a5p/Zn7E='
;
font-src 'self' data: https://fonts.gstatic.com;
img-src 'self';

View File

@ -7,7 +7,7 @@
script-src 'self';
connect-src 'self';
style-src 'self'
'sha256-ru2GY2rXeOf7PQX5LzK3ckNo21FCDUoRc2f3i0QcD1g='
'sha256-s7UPtBgvov5WNF9C1DlTZDpqwLgEmfiWha5a5p/Zn7E='
;
font-src 'self' data:;
img-src 'self' blob:;

View File

@ -1,13 +1,19 @@
function encodeChar(c) {
return '&#' + c.charCodeAt(0).toString(10) + ';';
return '&#' + c.codePointAt(0).toString(10) + ';';
}
function escapeHTML(text) {
return text.replace(/[^\r\n\t -%'-;=?-~]/g, encodeChar);
return text.replace(
/[\uD800-\uDBFF][\uDC00-\uDFFF]|[^\r\n\t -%'-;=?-~]/g,
encodeChar
);
}
function escapeQuoted(text) {
return text.replace(/[^\r\n\t !#$%(-;=?-~]/g, encodeChar);
return text.replace(
/[\uD800-\uDBFF][\uDC00-\uDFFF]|[^\r\n\t !#$%'-;=?-~]/g,
encodeChar
);
}
class TextNode {

View File

@ -236,9 +236,9 @@ describe('VirtualDocument', () => {
it('escapes attributes', () => {
const o = doc.createElement('div');
o.setAttribute('foo', 'b&a"r');
o.setAttribute('foo', 'b&a"\'r');
expect(o.outerHTML).toEqual('<div foo="b&#38;a&#34;r"></div>');
expect(o.outerHTML).toEqual('<div foo="b&#38;a&#34;\'r"></div>');
});
it('includes all children', () => {
@ -258,5 +258,12 @@ describe('VirtualDocument', () => {
expect(o.outerHTML).toEqual('<div>a&#60;b&#62;c</div>');
});
it('escapes non-BMP unicode characters', () => {
const o = doc.createElement('div');
o.appendChild(doc.createTextNode('\uD83D\uDE02'));
expect(o.outerHTML).toEqual('<div>&#128514;</div>');
});
});
});

View File

@ -62,6 +62,14 @@ function combineAttrs(activeCount, active) {
return attrs;
}
function shrinkWhitespace(text) {
return text.replace(/[\f\n\r\t\v ]+/g, ' ');
}
function trimCollapsible(text) {
return text.replace(/^[\f\n\r\t\v ]+|[\f\n\r\t\v ]+$/g, '');
}
export default function parseMarkdown(text) {
if(!text) {
return [];
@ -70,13 +78,14 @@ export default function parseMarkdown(text) {
const active = STYLES.map(() => false);
let activeCount = 0;
let attrs = null;
const lines = text.split('\n');
const lines = trimCollapsible(text).split('\n');
const result = [];
lines.forEach((line) => {
const ln = shrinkWhitespace(trimCollapsible(line));
const parts = [];
let p = 0;
for(;;) {
const {styleIndex, start, end} = findNext(line, p, active);
const {styleIndex, start, end} = findNext(ln, p, active);
if(styleIndex === -1) {
break;
}
@ -88,13 +97,13 @@ export default function parseMarkdown(text) {
++ activeCount;
}
if(start > p) {
parts.push({attrs, text: line.substring(p, start)});
parts.push({attrs, text: ln.substring(p, start)});
}
attrs = combineAttrs(activeCount, active);
p = end;
}
if(p < line.length) {
parts.push({attrs, text: line.substr(p)});
if(p < ln.length) {
parts.push({attrs, text: ln.substr(p)});
}
result.push(parts);
});

View File

@ -24,6 +24,33 @@ describe('Markdown Parser', () => {
]);
});
it('trims leading and trailing whitespace', () => {
const formatted = parser(' a \n \u00A0b \n ');
expect(formatted).toEqual([
[{attrs: null, text: 'a'}],
[{attrs: null, text: '\u00A0b'}],
]);
});
it('replaces sequences of whitespace with a single space', () => {
const formatted = parser('abc \t \v def');
expect(formatted).toEqual([
[{attrs: null, text: 'abc def'}],
]);
});
it('maintains internal blank lines', () => {
const formatted = parser('abc\n\ndef');
expect(formatted).toEqual([
[{attrs: null, text: 'abc'}],
[],
[{attrs: null, text: 'def'}],
]);
});
it('recognises bold styling', () => {
const formatted = parser('a **b** c __d__ e');

View File

@ -6,7 +6,7 @@ import Handlee from '../../fonts/HandleeFontData.mjs';
import Random from '../../core/Random.mjs';
const FONT = Handlee.name;
const FONT_FAMILY = '\'' + FONT + '\',cursive';
const FONT_FAMILY = FONT + ',cursive';
const LINE_HEIGHT = 1.5;
const MAX_CHAOS = 5;
@ -374,7 +374,7 @@ export default class SketchTheme extends BaseTheme {
// Font must be embedded for exporting as SVG / PNG
style.text(
'@font-face{' +
'font-family:"' + Handlee.name + '";' +
'font-family:' + Handlee.name + ';' +
'src:url("data:font/woff2;base64,' + Handlee.woff2 + '");' +
'}'
);

View File

@ -66,6 +66,16 @@ describe('SVGTextBlock', () => {
expect(hold.childNodes[1].innerHTML).toEqual('bar');
});
it('renders with tspans if the formatting changes', () => {
block.set({formatted: [[
{text: 'foo'},
{attrs: {zig: 'zag'}, text: 'bar'},
]]});
expect(hold.childNodes[0].innerHTML)
.toEqual('foo<tspan zig="zag">bar</tspan>');
});
it('re-uses text nodes when possible, adding more if needed', () => {
block.set({formatted: [[{text: 'foo'}], [{text: 'bar'}]]});
const [line0, line1] = hold.childNodes;