diff --git a/docs/font-inspector.html b/docs/font-inspector.html
index 52e1ea14..dea8fd6a 100644
--- a/docs/font-inspector.html
+++ b/docs/font-inspector.html
@@ -144,12 +144,24 @@
Free Software
var el = document.getElementById('message');
if (!message || message.trim().length === 0) {
el.style.display = 'none';
+ el.innerHTML = '';
} else {
el.style.display = 'block';
+ el.innerHTML = `${message}
`;
}
- el.innerHTML = message;
}
+function appendErrorMessage(message, type) {
+ var el = document.getElementById('message');
+ el.style.display = 'block';
+ el.innerHTML += `${message}
`;
+}
+
+document.addEventListener('opentypejs:message', function(event) {
+ const message = event.detail.message;
+ appendErrorMessage(message.toString(), message.type);
+});
+
function sortKeys(dict) {
var keys = [];
for (var key in dict) {
@@ -257,10 +269,11 @@ Free Software
}
try {
const data = await file.arrayBuffer();
- onFontLoaded(opentype.parse(isWoff2 ? Module.decompress(data) : data));
showErrorMessage('');
+ onFontLoaded(opentype.parse(isWoff2 ? Module.decompress(data) : data));
} catch (err) {
showErrorMessage(err.toString());
+ throw err;
}
}
form.file.onchange = function(e) {
diff --git a/docs/glyph-inspector.html b/docs/glyph-inspector.html
index 60732306..dd090554 100644
--- a/docs/glyph-inspector.html
+++ b/docs/glyph-inspector.html
@@ -92,12 +92,24 @@ Free Software
var el = document.getElementById('message');
if (!message || message.trim().length === 0) {
el.style.display = 'none';
+ el.innerHTML = '';
} else {
el.style.display = 'block';
+ el.innerHTML = `${message}
`;
}
- el.innerHTML = message;
}
+function appendErrorMessage(message, type) {
+ var el = document.getElementById('message');
+ el.style.display = 'block';
+ el.innerHTML += `${message}
`;
+}
+
+document.addEventListener('opentypejs:message', function(event) {
+ const message = event.detail.message;
+ appendErrorMessage(message.toString(), message.type);
+});
+
function pathCommandToString(cmd) {
var str = '' + cmd.type + ' ' +
((cmd.x !== undefined) ? 'x='+cmd.x+' y='+cmd.y+' ' : '') +
@@ -273,13 +285,19 @@ Free Software
var cellMarkSize = 4;
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, cellWidth, cellHeight);
- if (glyphIndex >= window.font.numGlyphs) return;
+ const nGlyphs = window.font.numGlyphs || window.font.nGlyphs;
+ if (glyphIndex >= nGlyphs) return;
ctx.fillStyle = '#606060';
ctx.font = '9px sans-serif';
ctx.fillText(glyphIndex, 1, cellHeight-1);
- var glyph = window.font.glyphs.get(glyphIndex),
- glyphWidth = glyph.advanceWidth * fontScale,
+ const glyph = window.font.glyphs.get(glyphIndex);
+ if (!glyph.advanceWidth) {
+ // force calculation of path data
+ glyph.getPath();
+ }
+ const advanceWidth = glyph.advanceWidth;
+ let glyphWidth = glyph.advanceWidth * fontScale,
xmin = (cellWidth - glyphWidth)/2,
xmax = (cellWidth + glyphWidth)/2,
x0 = xmin;
@@ -314,7 +332,7 @@ Free Software
h = glyphBgCanvas.height / pixelRatio,
glyphW = w - glyphMargin*2,
glyphH = h - glyphMargin*2,
- head = font.tables.head,
+ head = getFontDimensions(font),
maxHeight = head.yMax - head.yMin,
ctx = glyphBgCanvas.getContext('2d');
@@ -331,12 +349,23 @@ Free Software
ctx.clearRect(0, 0, w, h);
ctx.fillStyle = '#a0a0a0';
hline('Baseline', 0);
- hline('yMax', font.tables.head.yMax);
- hline('yMin', font.tables.head.yMin);
- hline('Ascender', font.tables.hhea.ascender);
- hline('Descender', font.tables.hhea.descender);
- hline('Typo Ascender', font.tables.os2.sTypoAscender);
- hline('Typo Descender', font.tables.os2.sTypoDescender);
+ hline('yMax', head.yMax);
+ hline('yMin', head.yMin);
+ hline('Ascender', font.tables.hhea ? font.tables.hhea.ascender : font.ascender || head.yMax);
+ hline('Descender', font.tables.hhea ? font.tables.hhea.descender : font.descender || head.yMin);
+ if (font.tables.os2) {
+ hline('Typo Ascender', font.tables.os2.sTypoAscender);
+ hline('Typo Descender', font.tables.os2.sTypoDescender);
+ }
+}
+
+function getFontDimensions(font) {
+ return font.isCFFFont ? {
+ xMin: font.tables.cff.topDict.fontBBox[0],
+ xMax: font.tables.cff.topDict.fontBBox[3] || 1000,
+ yMin: font.tables.cff.topDict.fontBBox[1] || -200,
+ yMax: font.tables.cff.topDict.fontBBox[2] || 1000
+ } :font.tables.head;
}
function onFontLoaded(font) {
@@ -344,7 +373,7 @@ Free Software
var w = cellWidth - cellMarginLeftRight * 2,
h = cellHeight - cellMarginTop - cellMarginBottom,
- head = font.tables.head,
+ head = getFontDimensions(font),
maxHeight = head.yMax - head.yMin;
fontScale = Math.min(w/(head.xMax - head.xMin), h/maxHeight);
fontSize = fontScale * font.unitsPerEm;
@@ -353,10 +382,11 @@ Free Software
var pagination = document.getElementById("pagination");
pagination.innerHTML = '';
var fragment = document.createDocumentFragment();
- var numPages = Math.ceil(font.numGlyphs / cellCount);
+ const nGlyphs = font.numGlyphs || font.nGlyphs;
+ var numPages = Math.ceil(nGlyphs / cellCount);
for(var i = 0; i < numPages; i++) {
var link = document.createElement('span');
- var lastIndex = Math.min(font.numGlyphs-1, (i+1)*cellCount-1);
+ var lastIndex = Math.min(nGlyphs-1, (i+1)*cellCount-1);
link.textContent = i*cellCount + '-' + lastIndex;
link.id = 'p' + i;
link.addEventListener('click', pageSelect, false);
@@ -378,7 +408,8 @@ Free Software
var firstGlyphIndex = pageSelected*cellCount,
cellIndex = +event.target.id.substr(1),
glyphIndex = firstGlyphIndex + cellIndex;
- if (glyphIndex < window.font.numGlyphs) {
+ const nGlyphs = window.font.numGlyphs || window.font.nGlyphs;
+ if (glyphIndex < nGlyphs) {
displayGlyph(glyphIndex);
displayGlyphData(glyphIndex);
}
@@ -410,10 +441,11 @@ Free Software
}
try {
const data = await file.arrayBuffer();
- onFontLoaded(opentype.parse(isWoff2 ? Module.decompress(data) : data));
showErrorMessage('');
+ onFontLoaded(opentype.parse(isWoff2 ? Module.decompress(data) : data));
} catch (err) {
showErrorMessage(err.toString());
+ throw err;
}
}
diff --git a/docs/index.html b/docs/index.html
index ed92e27e..34702b0a 100755
--- a/docs/index.html
+++ b/docs/index.html
@@ -169,12 +169,24 @@ Free Software
var el = document.getElementById('message');
if (!message || message.trim().length === 0) {
el.style.display = 'none';
+ el.innerHTML = '';
} else {
el.style.display = 'block';
+ el.innerHTML = `${message}
`;
}
- el.innerHTML = message;
}
+function appendErrorMessage(message, type) {
+ var el = document.getElementById('message');
+ el.style.display = 'block';
+ el.innerHTML += `${message}
`;
+}
+
+document.addEventListener('opentypejs:message', function(event) {
+ const message = event.detail.message;
+ appendErrorMessage(message.toString(), message.type);
+});
+
function onFontLoaded(font) {
window.font = font;
@@ -217,10 +229,11 @@ Free Software
}
try {
const data = await file.arrayBuffer();
- onFontLoaded(opentype.parse(isWoff2 ? Module.decompress(data) : data));
showErrorMessage('');
+ onFontLoaded(opentype.parse(isWoff2 ? Module.decompress(data) : data));
} catch (err) {
showErrorMessage(err.toString());
+ throw err;
}
}
diff --git a/docs/site.css b/docs/site.css
index 07693892..be42a0a7 100644
--- a/docs/site.css
+++ b/docs/site.css
@@ -107,14 +107,27 @@ canvas.text {
#message {
position: relative;
- top: -3px;
- background: red;
color: white;
- padding: 1px 5px;
font-weight: bold;
- border-radius: 2px;
display: none;
clear: both;
+ padding-top: 1px;
+}
+
+#message p {
+ margin: 2px 0;
+ padding: 2px 5px;
+ border-radius: 0.25rem;
+ border: solid 1px;
+ background: #fff3cd;
+ color: #856404;
+ border-color: #ffeeba;
+}
+
+#message p.message-type-1 {
+ background: #f8d7da;
+ color: #721c24;
+ border-color: #f5c6cb;
}
.message {
diff --git a/src/encoding.js b/src/encoding.js
index 865fad9f..eed40ba1 100644
--- a/src/encoding.js
+++ b/src/encoding.js
@@ -252,7 +252,9 @@ function addGlyphNamesAll(font) {
const c = charCodes[i];
const glyphIndex = glyphIndexMap[c];
glyph = font.glyphs.get(glyphIndex);
- glyph.addUnicode(parseInt(c));
+ if(glyph) {
+ glyph.addUnicode(parseInt(c));
+ }
}
for (let i = 0; i < font.glyphs.length; i += 1) {
diff --git a/src/font.js b/src/font.js
index 09d2dddc..af1e7a15 100644
--- a/src/font.js
+++ b/src/font.js
@@ -9,14 +9,15 @@ import Substitution from './substitution.js';
import { isBrowser, checkArgument } from './util.js';
import HintingTrueType from './hintingtt.js';
import Bidi from './bidi.js';
+import { logger, ErrorTypes, MessageLogger } from './logger.js';
function createDefaultNamesInfo(options) {
return {
fontFamily: {en: options.familyName || ' '},
fontSubfamily: {en: options.styleName || ' '},
- fullName: {en: options.fullName || options.familyName + ' ' + options.styleName},
+ fullName: {en: options.fullName || (options.familyName || '') + ' ' + (options.styleName || '')},
// postScriptName may not contain any whitespace
- postScriptName: {en: options.postScriptName || (options.familyName + options.styleName).replace(/\s/g, '')},
+ postScriptName: {en: options.postScriptName || ((options.familyName || '') + (options.styleName || '')).replace(/\s/g, '')},
designer: {en: options.designer || ' '},
designerURL: {en: options.designerURL || ' '},
manufacturer: {en: options.manufacturer || ' '},
@@ -502,14 +503,17 @@ Font.prototype.getEnglishName = function(name) {
/**
* Validate
+ * @type {MessageLogger}
*/
+Font.prototype.validation = new MessageLogger();
+Font.prototype.ErrorTypes = ErrorTypes;
Font.prototype.validate = function() {
- const warnings = [];
+ const validationMessages = [];
const _this = this;
function assert(predicate, message) {
if (!predicate) {
- warnings.push(message);
+ validationMessages.push(_this.validation.add(message, _this.ErrorTypes.WARNING));
}
}
@@ -528,6 +532,8 @@ Font.prototype.validate = function() {
// Dimension information
assert(this.unitsPerEm > 0, 'No unitsPerEm specified.');
+
+ return validationMessages;
};
/**
@@ -542,7 +548,7 @@ Font.prototype.toTables = function() {
* @deprecated Font.toBuffer is deprecated. Use Font.toArrayBuffer instead.
*/
Font.prototype.toBuffer = function() {
- console.warn('Font.toBuffer is deprecated. Use Font.toArrayBuffer instead.');
+ logger.add('Font.toBuffer is deprecated. Use Font.toArrayBuffer instead.', this.ErrorTypes.DEPRECATED);
return this.toArrayBuffer();
};
/**
@@ -585,7 +591,7 @@ Font.prototype.download = function(fileName) {
event.initEvent('click', true, false);
link.dispatchEvent(event);
} else {
- console.warn('Font file could not be downloaded. Try using a different browser.');
+ logger.add('Font file could not be downloaded. Try using a different browser.');
}
} else {
const fs = require('fs');
@@ -658,3 +664,4 @@ Font.prototype.usWeightClasses = {
};
export default Font;
+export { createDefaultNamesInfo };
\ No newline at end of file
diff --git a/src/glyphset.js b/src/glyphset.js
index adddb395..f70d466c 100644
--- a/src/glyphset.js
+++ b/src/glyphset.js
@@ -1,6 +1,7 @@
// The GlyphSet object
import Glyph from './glyph.js';
+import { logger } from './logger.js';
// Define a property on the glyph that depends on the path being loaded.
function defineDependentProperty(glyph, externalName, internalName) {
@@ -37,7 +38,6 @@ function GlyphSet(font, glyphs) {
this.glyphs[i] = glyph;
}
}
-
this.length = (glyphs && glyphs.length) || 0;
}
@@ -64,13 +64,20 @@ if(typeof Symbol !== 'undefined' && Symbol.iterator) {
GlyphSet.prototype.get = function(index) {
// this.glyphs[index] is 'undefined' when low memory mode is on. glyph is pushed on request only.
if (this.glyphs[index] === undefined) {
+ if (typeof this.font._push !== 'function') {
+ if (index !== null) {
+ logger.add(`Trying to access unknown glyph at index ${index}`, logger.ErrorTypes.WARNING);
+ }
+ return;
+ }
+
this.font._push(index);
if (typeof this.glyphs[index] === 'function') {
this.glyphs[index] = this.glyphs[index]();
}
let glyph = this.glyphs[index];
- let unicodeObj = this.font._IndexToUnicodeMap[index];
+ let unicodeObj = this.font._IndexToUnicodeMap && this.font._IndexToUnicodeMap[index];
if (unicodeObj) {
for (let j = 0; j < unicodeObj.unicodes.length; j++)
diff --git a/src/logger.js b/src/logger.js
new file mode 100644
index 00000000..3dfe110e
--- /dev/null
+++ b/src/logger.js
@@ -0,0 +1,144 @@
+import { isBrowser } from './util.js';
+
+/**
+ * @typedef {number} ErrorTypes
+ */
+
+/**
+ * @enum {ErrorTypes}
+ */
+const ErrorTypes = {
+ ERROR: 1,
+ WARNING: 2,
+ DEPRECATED: 4,
+ ALL: 32767
+};
+Object.freeze && Object.freeze(ErrorTypes);
+
+/**
+ * @enum {ErrorStrings}
+ */
+const errorStrings = {
+ 1: 'ERROR',
+ 2: 'WARNING',
+ 4: 'DEPRECATED'
+};
+
+const logMethods = {
+ 1: 'error',
+ 2: 'warn',
+ 4: 'info'
+};
+
+/**
+ * @property {string} string - message string
+ * @property {keyof ErrorTypes} type - error type
+ */
+class Message {
+ constructor(string, type = ErrorTypes.ERROR) {
+ if (!errorStrings[type]) {
+ throw new Error( 'Invalid error type ' + type + ' for message: ' + string );
+ }
+
+ this.string = string;
+ this.type = type;
+ }
+
+ toString() {
+ return errorStrings[this.type] + ': ' + this.string;
+ }
+}
+
+class MessageLogger {
+
+ constructor() {
+ this.logLevel = ErrorTypes.ALL;
+ this.throwLevel = ErrorTypes.ERROR;
+ this.ErrorTypes = ErrorTypes;
+ }
+
+ /**
+ * Logs a message and fires the opentypejs:message Event.
+ * @property {String|Message} string
+ * @property {keyof ErrorTypes} type
+ *
+ * @returns {Message}
+ */
+ add(stringOrMessage, type = ErrorTypes.ERROR) {
+ let message;
+ if (stringOrMessage instanceof Message) {
+ message = stringOrMessage;
+ type = message.type;
+ } else {
+ message = new Message(stringOrMessage, type);
+ }
+
+ let doLog = !!(this.logLevel & type);
+
+ if (isBrowser()) {
+ const messageEvent = new CustomEvent('opentypejs:message', {
+ cancelable: true,
+ detail: {
+ message,
+ doLog: doLog,
+ logger: this.logLevel
+ }
+ });
+ const cancelled = document.dispatchEvent(messageEvent);
+ if (cancelled) {
+ doLog = false;
+ }
+ }
+
+ if (doLog) {
+ this.logMessage(message);
+ }
+
+ return message;
+ }
+
+ /**
+ * adds an array of messages
+ */
+ adds(messageArray) {
+ for (let i = 0; i < messageArray.length; i++) {
+ this.add(messageArray[i]);
+ }
+ }
+
+ /**
+ * Logs a message to the console or throws it,
+ * depending on the throwLevel setting.
+ * @param {Message} message
+ */
+ logMessage(message) {
+ const type = message.type || ErrorTypes.ERROR;
+ const logMethod = console[logMethods[type] || 'log'] || console.log;
+ const logMessage = '[opentype.js] ' + message.toString();
+ if ( this.throwLevel & type ) {
+ throw new Error(logMessage);
+ }
+ logMethod(logMessage);
+ }
+
+ getLogLevel() {
+ return this.logLevel;
+ }
+
+ setLogLevel(newLevel) {
+ this.logLevel = newLevel;
+ }
+
+ getThrowLevel() {
+ return this.throwLevel;
+ }
+
+ setThrowLevel(newLevel) {
+ this.throwLevel = newLevel;
+ }
+
+}
+
+const globalLogger = new MessageLogger();
+
+export { ErrorTypes, Message, MessageLogger, globalLogger as logger };
\ No newline at end of file
diff --git a/src/opentype.js b/src/opentype.js
index 0fd00854..71f013b2 100644
--- a/src/opentype.js
+++ b/src/opentype.js
@@ -35,6 +35,9 @@ import os2 from './tables/os2.js';
import post from './tables/post.js';
import meta from './tables/meta.js';
import gasp from './tables/gasp.js';
+import { createDefaultNamesInfo } from './font.js';
+import { sizeOf } from './types.js';
+import { ErrorTypes, logger } from './logger.js';
/**
* The opentype library.
* @namespace opentype
@@ -187,17 +190,18 @@ function parseWOFFTableEntries(data, numTables) {
*/
/**
+ * @param {opentype.Font}
* @param {DataView}
* @param {Object}
* @return {TableData}
*/
-function uncompressTable(data, tableEntry) {
+function uncompressTable(data, tableEntry) {
if (tableEntry.compression === 'WOFF') {
const inBuffer = new Uint8Array(data.buffer, tableEntry.offset + 2, tableEntry.compressedLength - 2);
const outBuffer = new Uint8Array(tableEntry.length);
inflate(inBuffer, outBuffer);
if (outBuffer.byteLength !== tableEntry.length) {
- throw new Error('Decompression error: ' + tableEntry.tag + ' decompressed length doesn\'t match recorded length');
+ logger.add('Decompression error: ' + tableEntry.tag + ' decompressed length doesn\'t match recorded length');
}
const view = new DataView(outBuffer.buffer, 0);
@@ -249,16 +253,26 @@ function parseBuffer(buffer, opt={}) {
} else if (flavor === 'OTTO') {
font.outlinesFormat = 'cff';
} else {
- throw new Error('Unsupported OpenType flavor ' + signature);
+ logger.add('Unsupported OpenType flavor ' + signature);
}
numTables = parse.getUShort(data, 12);
tableEntries = parseWOFFTableEntries(data, numTables);
} else if (signature === 'wOF2') {
var issue = 'https://github.com/opentypejs/opentype.js/issues/183#issuecomment-1147228025';
- throw new Error('WOFF2 require an external decompressor library, see examples at: ' + issue);
+ logger.add('WOFF2 require an external decompressor library, see examples at: ' + issue);
+ } else if (signature.substring(0,2) === '%!') {
+ // https://adobe-type-tools.github.io/font-tech-notes/pdfs/T1_SPEC.pdf
+ // https://personal.math.ubc.ca/~cass/piscript/type1.pdf
+ logger.add('PostScript/PS1/T1/Adobe Type 1 fonts are not supported');
+ } else if (data.buffer.byteLength > (3 * sizeOf.Card8() + sizeOf.OffSize()) && parse.getByte(data, 0) === 0x01) {
+ // this could be a CFF1 file, we will try to parse it like a CCF table below
+ // https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf
+ font.isCFFFont = true;
+ tableEntries.push({tag:'CFF ',offset:0});
+ numTables = 1;
} else {
- throw new Error('Unsupported OpenType signature ' + signature);
+ logger.add('Unsupported OpenType signature ' + signature);
}
let cffTableEntry;
@@ -393,9 +407,16 @@ function parseBuffer(buffer, opt={}) {
}
}
- const nameTable = uncompressTable(data, nameTableEntry);
- font.tables.name = _name.parse(nameTable.data, nameTable.offset, ltagTable);
- font.names = font.tables.name;
+ if ( nameTableEntry ) {
+ const nameTable = uncompressTable(data, nameTableEntry);
+ font.tables.name = _name.parse(nameTable.data, nameTable.offset, ltagTable);
+ font.names = font.tables.name;
+ } else {
+ font.names = {};
+ font.names.unicode = createDefaultNamesInfo({});
+ font.names.macintosh = createDefaultNamesInfo({});
+ font.names.windows = createDefaultNamesInfo({});
+ }
if (glyfTableEntry && locaTableEntry) {
const shortVersion = indexToLocFormat === 0;
@@ -410,12 +431,21 @@ function parseBuffer(buffer, opt={}) {
const cffTable2 = uncompressTable(data, cff2TableEntry);
cff.parse(cffTable2.data, cffTable2.offset, font, opt);
} else {
- throw new Error('Font doesn\'t contain TrueType, CFF or CFF2 outlines.');
+ logger.add('Font doesn\'t contain TrueType, CFF or CFF2 outlines.');
}
- const hmtxTable = uncompressTable(data, hmtxTableEntry);
- hmtx.parse(font, hmtxTable.data, hmtxTable.offset, font.numberOfHMetrics, font.numGlyphs, font.glyphs, opt);
- addGlyphNames(font, opt);
+ if (hmtxTableEntry) {
+ const hmtxTable = uncompressTable(data, hmtxTableEntry);
+ hmtx.parse(font, hmtxTable.data, hmtxTable.offset, font.numberOfHMetrics, font.numGlyphs, font.glyphs, opt);
+ }
+
+ if (!font.tables.cmap) {
+ if (!font.isCFFFont) {
+ logger.add('Font doesn\'t contain required cmap table', ErrorTypes.WARNING);
+ }
+ } else {
+ addGlyphNames(font, opt);
+ }
if (kernTableEntry) {
const kernTable = uncompressTable(data, kernTableEntry);
@@ -540,5 +570,6 @@ export {
parse as _parse,
parseBuffer as parse,
load,
- loadSync
+ loadSync,
+ ErrorTypes
};
diff --git a/src/parse.js b/src/parse.js
index d6770721..e0bd9cb4 100644
--- a/src/parse.js
+++ b/src/parse.js
@@ -37,15 +37,29 @@ function getFixed(dataView, offset) {
return decimal + fraction / 65535;
}
+// Retrieve a string with a specific byte length from the DataView.
+function getString(dataView, offset, length) {
+ let string = '';
+
+ if (!offset) {
+ offset = 0;
+ }
+
+ if (length === undefined) {
+ length = dataView.byteLength - offset;
+ }
+
+ for (let i = offset; i < offset + length; i += 1) {
+ string += String.fromCharCode(dataView.getInt8(i));
+ }
+
+ return string;
+}
+
// Retrieve a 4-character tag from the DataView.
// Tags are used to identify tables.
function getTag(dataView, offset) {
- let tag = '';
- for (let i = offset; i < offset + 4; i += 1) {
- tag += String.fromCharCode(dataView.getInt8(i));
- }
-
- return tag;
+ return getString(dataView, offset, 4);
}
// Retrieve an offset from the DataView.
@@ -721,6 +735,7 @@ export default {
getUInt24,
getULong,
getFixed,
+ getString,
getTag,
getOffset,
getBytes,
diff --git a/src/tables/cff.js b/src/tables/cff.js
index 51a82986..58aaad18 100755
--- a/src/tables/cff.js
+++ b/src/tables/cff.js
@@ -10,6 +10,8 @@ import glyphset from '../glyphset.js';
import parse from '../parse.js';
import Path from '../path.js';
import table from '../table.js';
+import { createDefaultNamesInfo } from '../font.js';
+import { logger, ErrorTypes } from '../logger.js';
// Custom equals function that can also check lists.
function equals(a, b) {
@@ -309,7 +311,7 @@ function interpretDict(dict, meta, strings) {
}
// Parse the CFF header.
-function parseCFFHeader(data, start) {
+function parseCFFHeader(data, start, isCFFFont) {
const header = {};
header.formatMajor = parse.getCard8(data, start);
header.formatMinor = parse.getCard8(data, start + 1);
@@ -323,7 +325,7 @@ function parseCFFHeader(data, start) {
if (header.formatMajor < 2) {
header.offsetSize = parse.getCard8(data, start + 3);
header.startOffset = start;
- header.endOffset = start + 4;
+ header.endOffset = start + (isCFFFont ? header.size : 4);
} else {
header.topDictLength = parse.getCard16(data, start + 3);
header.endOffset = start + 8;
@@ -1101,17 +1103,17 @@ function parseCFFCharstring(font, glyph, code, version) {
return p;
}
-function parseCFFFDSelect(data, start, nGlyphs, fdArrayCount, version) {
+function parseCFFFDSelect(data, start, font, fdArrayCount, version) {
const fdSelect = [];
let fdIndex;
const parser = new parse.Parser(data, start);
const format = parser.parseCard8();
if (format === 0) {
// Simple list of nGlyphs elements
- for (let iGid = 0; iGid < nGlyphs; iGid++) {
+ for (let iGid = 0; iGid < font.nGlyphs; iGid++) {
fdIndex = parser.parseCard8();
if (fdIndex >= fdArrayCount) {
- throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')');
+ logger.add('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')');
}
fdSelect.push(fdIndex);
}
@@ -1120,36 +1122,43 @@ function parseCFFFDSelect(data, start, nGlyphs, fdArrayCount, version) {
const nRanges = format === 4 ? parser.parseULong() : parser.parseCard16();
let first = format === 4 ? parser.parseULong() : parser.parseCard16();
if (first !== 0) {
- throw new Error(`CFF Table CID Font FDSelect format ${format} range has bad initial GID ${first}`);
+ logger.add(`CFF Table CID Font FDSelect format ${format} range has bad initial GID ${first}`);
}
let next;
for (let iRange = 0; iRange < nRanges; iRange++) {
fdIndex = format === 4 ? parser.parseUShort() : parser.parseCard8();
next = format === 4 ? parser.parseULong() : parser.parseCard16();
if (fdIndex >= fdArrayCount) {
- throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')');
+ logger.add('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')');
}
- if (next > nGlyphs) {
- throw new Error(`CFF Table CID Font FDSelect format ${version} range has bad GID ${next}`);
+ if (next > font.nGlyphs) {
+ logger.add(`CFF Table CID Font FDSelect format ${version} range has bad GID ${next}`);
}
for (; first < next; first++) {
fdSelect.push(fdIndex);
}
first = next;
}
- if (next !== nGlyphs) {
- throw new Error('CFF Table CID Font FDSelect format 3 range has bad final (Sentinal) GID ' + next);
+ if (next !== font.nGlyphs) {
+ logger.add('CFF Table CID Font FDSelect format 3 range has bad final (Sentinel) GID ' + next, ErrorTypes.WARNING);
}
} else {
- throw new Error('CFF Table CID Font FDSelect table has unsupported format ' + format);
+ logger.add('CFF Table CID Font FDSelect table has unsupported format ' + format);
}
+
return fdSelect;
}
-// Parse the `CFF` table, which contains the glyph outlines in PostScript format.
+/**
+ * Parse the `CFF` table, which contains the glyph outlines in PostScript format.
+ * @param {DataView} data
+ * @param {Number} start
+ * @param {Font} font
+ * @param {Object} opt
+ */
function parseCFFTable(data, start, font, opt) {
let resultTable;
- const header = parseCFFHeader(data, start);
+ const header = parseCFFHeader(data, start, !!font.isCFFFont);
if (header.formatMajor === 2) {
resultTable = font.tables.cff2 = {};
} else {
@@ -1170,13 +1179,14 @@ function parseCFFTable(data, start, font, opt) {
} else {
const topDictArray = gatherCFFTopDicts(data, start, topDictIndex.objects, stringIndex.objects, header.formatMajor);
if (topDictArray.length !== 1) {
- throw new Error('CFF table has too many fonts in \'FontSet\' - count of fonts NameIndex.length = ' + topDictArray.length);
+ logger.add('CFF table has too many fonts in \'FontSet\' - count of fonts NameIndex.length = ' + topDictArray.length);
}
topDict = topDictArray[0];
}
resultTable.topDict = topDict;
+ resultTable.nameIndex = nameIndex;
if (topDict._privateDict) {
font.defaultWidthX = topDict._privateDict.defaultWidthX;
@@ -1187,11 +1197,22 @@ function parseCFFTable(data, start, font, opt) {
font.isCIDFont = true;
}
+ // CharStrings must be parsed before FDSelect, because we need the nGlyphs value for parsing FDSelect
+ // Offsets in the top dict are relative to the beginning of the CFF data, so add the CFF start offset.
+ let charStringsIndex;
+ if (opt.lowMemory) {
+ charStringsIndex = parseCFFIndexLowMemory(data, start + topDict.charStrings, header.formatMajor);
+ font.nGlyphs = charStringsIndex.offsets.length - (header.formatMajor > 1 ? 1 : 0); // number of elements is count + 1
+ } else {
+ charStringsIndex = parseCFFIndex(data, start + topDict.charStrings, null, header.formatMajor);
+ font.nGlyphs = charStringsIndex.objects.length;
+ }
+
if ( header.formatMajor > 1 ) {
let fdArrayIndexOffset = topDict.fdArray;
let fdSelectOffset = topDict.fdSelect;
if (!fdArrayIndexOffset) {
- throw new Error('This is a CFF2 font, but FDArray information is missing');
+ logger.add('This is a CFF2 font, but FDArray information is missing');
}
const fdArrayIndex = parseCFFIndex(data, start + fdArrayIndexOffset, null, header.formatMajor);
@@ -1199,21 +1220,21 @@ function parseCFFTable(data, start, font, opt) {
const fdArray = gatherCFF2FontDicts(data, start, fdArrayIndex.objects);
topDict._fdArray = fdArray;
if (fdSelectOffset) {
- topDict._fdSelect = parseCFFFDSelect(data, start + fdSelectOffset, font.numGlyphs, fdArray.length, header.formatMajor);
+ topDict._fdSelect = parseCFFFDSelect(data, start + fdSelectOffset, font, fdArray.length, header.formatMajor);
}
} else if (font.isCIDFont) {
let fdArrayOffset = topDict.fdArray;
let fdSelectOffset = topDict.fdSelect;
if (fdArrayOffset === 0 || fdSelectOffset === 0) {
- throw new Error('Font is marked as a CID font, but FDArray and/or FDSelect information is missing');
+ logger.add('Font is marked as a CID font, but FDArray and/or FDSelect information is missing');
}
fdArrayOffset += start;
const fdArrayIndex = parseCFFIndex(data, fdArrayOffset);
const fdArray = gatherCFFTopDicts(data, start, fdArrayIndex.objects, stringIndex.objects, header.formatMajor);
topDict._fdArray = fdArray;
fdSelectOffset += start;
- topDict._fdSelect = parseCFFFDSelect(data, fdSelectOffset, font.numGlyphs, fdArray.length, header.formatMajor);
+ topDict._fdSelect = parseCFFFDSelect(data, fdSelectOffset, font, fdArray.length, header.formatMajor);
}
if (header.formatMajor < 2) {
@@ -1233,18 +1254,8 @@ function parseCFFTable(data, start, font, opt) {
}
}
- // Offsets in the top dict are relative to the beginning of the CFF data, so add the CFF start offset.
- let charStringsIndex;
- if (opt.lowMemory) {
- charStringsIndex = parseCFFIndexLowMemory(data, start + topDict.charStrings, header.formatMajor);
- font.nGlyphs = charStringsIndex.offsets.length - (header.formatMajor > 1 ? 1 : 0); // number of elements is count + 1
- } else {
- charStringsIndex = parseCFFIndex(data, start + topDict.charStrings, null, header.formatMajor);
- font.nGlyphs = charStringsIndex.objects.length;
- }
-
if ( header.formatMajor > 1 && font.tables.maxp && font.nGlyphs !== font.tables.maxp.numGlyphs ) {
- console.error(`Glyph count in the CFF2 table (${font.nGlyphs}) must correspond to the glyph count in the maxp table (${font.tables.maxp.numGlyphs})`);
+ logger.add(`Glyph count in the CFF2 table (${font.nGlyphs}) must correspond to the glyph count in the maxp table (${font.tables.maxp.numGlyphs})`, ErrorTypes.WARNING);
}
if (header.formatMajor < 2) {
@@ -1280,6 +1291,27 @@ function parseCFFTable(data, start, font, opt) {
const p = new parse.Parser(data, start + topDict.vstore);
topDict._vstore = p.parseVariationStore();
}
+
+ if (font.isCFFFont) {
+ logger.add('CFF Type1 fonts are not fully supported, but you can use this to extract glyph outlines and metadata for example.', ErrorTypes.WARNING);
+ const topDict = font.tables.cff.topDict;
+ const psName = font.tables.cff.nameIndex && font.tables.cff.nameIndex.objects.length && font.tables.cff.nameIndex.objects[0] || '';
+ const metaData = {
+ copyright: topDict.copyright || topDict.notice,
+ fullName: topDict.fullName,
+ version: topDict.version,
+ postScriptName: psName
+ };
+ font.names.unicode = createDefaultNamesInfo(metaData);
+ font.names.macintosh = createDefaultNamesInfo(metaData);
+ font.names.windows = createDefaultNamesInfo(metaData);
+
+ const bBox = topDict.fontBBox;
+ const fMatrix = topDict.fontMatrix;
+ font.ascender = bBox && bBox.length > 2 && bBox[2] || 0;
+ font.descender = bBox && bBox.length > 1 && bBox[1] || 0;
+ font.unitsPerEm = fMatrix && fMatrix.length && (1/fMatrix[0]) || 1000;
+ }
}
// Convert a string to a String ID (SID).
diff --git a/src/tables/cmap.js b/src/tables/cmap.js
index 3130b051..6dfbae15 100644
--- a/src/tables/cmap.js
+++ b/src/tables/cmap.js
@@ -6,7 +6,7 @@ import parse from '../parse.js';
import table from '../table.js';
import { eightBitMacEncodings } from '../types.js';
import { getEncoding } from '../tables/name.js';
-
+
function parseCmapTableFormat0(cmap, p, platformID, encodingID) {
// Length in bytes of the index map
cmap.length = p.parseUShort();
@@ -199,7 +199,8 @@ function parseCmapTable(data, start) {
if (offset === -1) {
// There is no cmap table in the font that we support.
- throw new Error('No valid cmap sub-tables found.');
+ // logging will be handled down the line if return now
+ return;
}
const p = new parse.Parser(data, start + offset);
diff --git a/src/types.js b/src/types.js
index d8e5e637..94ced906 100644
--- a/src/types.js
+++ b/src/types.js
@@ -2,6 +2,7 @@
// All OpenType fonts use Motorola-style byte ordering (Big Endian)
import check from './check.js';
+import { logger } from './logger.js';
const LIMIT16 = 32768; // The limit at which a 16-bit number switches signs == 2^15
const LIMIT32 = 2147483648; // The limit at which a 32-bit number switches signs == 2 ^ 31
@@ -70,9 +71,9 @@ sizeOf.CHAR = constant(1);
* @returns {Array}
*/
encode.CHARARRAY = function(v) {
- if (typeof v === 'undefined') {
+ if (v == null) { // catches undefined and null
v = '';
- console.warn('Undefined CHARARRAY encountered and treated as an empty string. This is probably caused by a missing glyph name.');
+ logger.add('Undefined CHARARRAY encountered and treated as an empty string. This is probably caused by a missing glyph name.', logger.ErrorTypes.WARNING);
}
const b = [];
for (let i = 0; i < v.length; i += 1) {
@@ -183,7 +184,7 @@ sizeOf.LONG = constant(4);
*/
encode.FLOAT = function(v) {
if (v > MAX_16_16 || v < MIN_16_16) {
- throw new Error(`Value ${v} is outside the range of representable values in 16.16 format`);
+ logger.add(`Value ${v} is outside the range of representable values in 16.16 format`, logger.ErrorTypes.ERROR);
}
const fixedValue = Math.round(v * (1 << 16)) << 0; // Round to nearest multiple of 1/(1<<16)
return encode.ULONG(fixedValue);
@@ -856,7 +857,7 @@ encode.OPERAND = function(v, type) {
d.push(enc1[j]);
}
} else {
- throw new Error('Unknown operand type ' + type);
+ logger.add('Unknown operand type ' + type, logger.ErrorTypes.ERROR);
// FIXME Add support for booleans
}
}