From f41a25d8f06fd8f01c53ab78597db0b7641dd8f1 Mon Sep 17 00:00:00 2001 From: Vaclav Lunak Date: Fri, 3 Sep 2021 12:32:59 +0200 Subject: [PATCH 01/10] head table --- src/Untypr.js | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 src/Untypr.js diff --git a/src/Untypr.js b/src/Untypr.js new file mode 100644 index 0000000..fa6a6e2 --- /dev/null +++ b/src/Untypr.js @@ -0,0 +1,101 @@ +var Untypr = {}; + + +Untypr["T"] = {}; + +Untypr["T"].head = { + encodeTab: function(obj, offset, buff) + { + var bin = Typr["B"]; + bin.writeUint16(buff, offset, 1); offset += 2; // majorVersion + bin.writeUint16(buff, offset, 0); offset += 2; // minorVersion + bin.writeFixed(buff, offset, obj["fontRevision"]); offset += 4; + var checkSumOffset = offset; // TODO + bin.writeUint(buff, offset, 0); offset += 4; // checkSumAdjustment + bin.writeUint(buff, offset, 0x5f0f3cf5); offset += 4; // magic constant + bin.writeUint(buff, offset, obj["flags"]); offset += 2; + bin.writeUshort(buff, offset, obj["unitsPerEm"]); offset += 2; + bin.writeUint64(buff, offset, obj["created"]); offset += 8; + bin.writeUint64(buff, offset, obj["modified"]); offset += 8; + bin.writeShort(buff, offset, obj["xMin"]); offset += 2; + bin.writeShort(buff, offset, obj["yMin"]); offset += 2; + bin.writeShort(buff, offset, obj["xMax"]); offset += 2; + bin.writeShort(buff, offset, obj["yMax"]); offset += 2; + bin.writeUint16(buff, offset, obj["macStyle"]); offset += 2; + bin.writeUint16(buff, offset, obj["lowestRecPPEM"]); offset += 2; + bin.writeShort(buff, offset, obj["fontDirectionHint"]); offset += 2; + bin.writeShort(buff, offset, obj["indexToLocFormat"]); offset += 2; + bin.writeShort(buff, offset, obj["glyphDataFormat"]); + } +} + + +Untypr["B"] = { + writeUint: function(buff, p, n) + { + buff[p] = (n>>24)&255; buff[p+1] = (n>>16)&255; buff[p+2] = (n>>8)&255; buff[p+3] = n&255; + }, + writeUint16: function(buff, p, n) + { + buff[p] = (n>>8)&255; buff[p+1] = n&255; + }, + writeUint8: function(buff, p, n) + { + buff[p] = n&255; + }, + writeUint64: function(buff, p, n) + { + // TODO + }, + writeInt: function(buff, p, n) + { + var a = Typr["B"].t.uint8; + Typr["B"].t.int32[0] = n; + buff[p] = a[3]; + buff[p+1] = a[2]; + buff[p+2] = a[1]; + buff[p+3] = a[0]; + }, + writeShort: function(buff, p, n) + { + var a = Typr["B"].t.uint8; + Typr["B"].t.int16[0] = n; + buff[p] = a[1]; + buff[p+1] = a[0]; + }, + writeInt8: function(buff, p, n) + { + var a = Typr["B"].t.uint8; + Typr["B"].t.int8[0] = n; + buff[p] = a[0]; + }, + writeInt64: function(buff, p, n) + { + // TODO + }, + writeF2dot14: function(buff, p, n) + { + Typr["B"].writeShort(buff, p, n * 16384) + }, + writeFixed: function(buff, p, n) + { + // TODO + }, + writeBytes: function(buff, p, arr) + { + for(var i=0; i Date: Fri, 3 Sep 2021 18:22:07 +0200 Subject: [PATCH 02/10] added most required tables --- src/Untypr.js | 254 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 245 insertions(+), 9 deletions(-) diff --git a/src/Untypr.js b/src/Untypr.js index fa6a6e2..ae06f63 100644 --- a/src/Untypr.js +++ b/src/Untypr.js @@ -6,7 +6,7 @@ Untypr["T"] = {}; Untypr["T"].head = { encodeTab: function(obj, offset, buff) { - var bin = Typr["B"]; + var bin = Untypr["B"]; bin.writeUint16(buff, offset, 1); offset += 2; // majorVersion bin.writeUint16(buff, offset, 0); offset += 2; // minorVersion bin.writeFixed(buff, offset, obj["fontRevision"]); offset += 4; @@ -27,8 +27,234 @@ Untypr["T"].head = { bin.writeShort(buff, offset, obj["indexToLocFormat"]); offset += 2; bin.writeShort(buff, offset, obj["glyphDataFormat"]); } +}; + +Untypr["T"].hhea = { + encodeTab: function(obj, offset, buff) + { + var bin = Untypr["B"]; + bin.writeUint16(buff, offset, 1); offset += 2;// major version; + bin.writeUint16(buff, offset, 0); offset += 2;// minor version; + + var keys = ["ascender","descender","lineGap", + "advanceWidthMax","minLeftSideBearing","minRightSideBearing","xMaxExtent", + "caretSlopeRise","caretSlopeRun","caretOffset", + undefined,undefined,undefined,undefined, + "metricDataFormat","numberOfHMetrics" ]; + + for(var i=0; i[0-9]+),(?[0-9A-Fa-f]+)/) + if (!match) continue; + + var pID = Number.parseInt(match.platformID, 10); + var lID = Number.parseInt(match.languageID, 16); + // FIXME: We just use Unicode or pick Roman and hope + var encID; + if (pID == 0) encID = 4; // Unicode, full Unicode + else if (pID == 1) encID = 0; // Macintosh, Roman + else if (pID == 3) encID = 10; // Windows, full Unicode + + for (var name in Object.keys(obj[key])) { + var nID = names.indexOf(name); + if (nID != -1) records.push({ pID, eID, lID, nID, key }); + } + } + + // name records have to be sorted based on IDs + records.sort((a, b) => { + if (a.pID < b.pID) return -1; + if (a.pID > b.pID) return 1; + if (a.eID < b.eID) return -1; + if (a.eID > b.eID) return 1; + if (a.lID < b.lID) return -1; + if (a.lID > b.lID) return 1; + if (a.nID < b.nID) return -1; + if (a.nID > b.nID) return 1; + return 0; + }); + + for (var r of records) { + bin.writeUint16(buff, offset, r.pID); offset += 2; + bin.writeUint16(buff, offset, r.eID); offset += 2; + bin.writeUint16(buff, offset, r.lID); offset += 2; + bin.writeUint16(buff, offset, r.nID); offset += 2; + bin.writeUint16(buff, offset, obj[r.key][names[r.nID]].length); offset += 2; + bin.writeUint16(buff, offset, 0); offset += 2; // TODO add offsets; + } + + for (r of records) { + var func = (r.pID == 1 && r.eID == 0) ? bin.writeASCII : bin.writeUnicode; + func(buff, offset, obj[r.key][names[r.nID]]); + } + } +} + +Untypr["T"].OS2 = { + encodeTab: function(obj, offset, buff) { + var bin = Untypr["B"]; + // Typr doesn't store table version, so we have to guess it from contents + var version; + if (obj.hasOwnProperty("usLowerOpticalPointSize")) version = 5; + else if (obj.hasOwnProperty("sxHeight")) version = 2; + else if (obj.hasOwnProperty("ulCodePageRange1")) version = 1; + else version = 0; + + bin.writeUint16(buff, offset, version); offset += 2; + bin.writeShort(buff, offset, obj["xAvgCharWidth"]); offset += 2; + bin.writeUint16(buff, offset, obj["usWeightClass"]); offset += 2; + bin.writeUint16(buff, offset, obj["usWidthClass"]); offset += 2; + bin.writeUint16(buff, offset, obj["fsType"]); offset += 2; + bin.writeShort(buff, offset, obj["ySubscriptXSize"]); offset += 2; + bin.writeShort(buff, offset, obj["ySubscriptYSize"]); offset += 2; + bin.writeShort(buff, offset, obj["ySubscriptXOffset"]); offset += 2; + bin.writeShort(buff, offset, obj["ySubscriptYOffset"]); offset += 2; + bin.writeShort(buff, offset, obj["ySuperscriptXSize"]); offset += 2; + bin.writeShort(buff, offset, obj["ySuperscriptYSize"]); offset += 2; + bin.writeShort(buff, offset, obj["ySuperscriptXOffset"]); offset += 2; + bin.writeShort(buff, offset, obj["ySuperscriptYOffset"]); offset += 2; + bin.writeShort(buff, offset, obj["yStrikeoutSize"]); offset += 2; + bin.writeShort(buff, offset, obj["yStrikeoutPosition"]); offset += 2; + bin.writeShort(buff, offset, obj["sFamilyClass"]); offset += 2; + bin.writeBytes(buff, offset, obj["panose"]); offset += 10; + bin.writeUint(buff, offset, obj["ulUnicodeRange1"]); offset += 4; + bin.writeUint(buff, offset, obj["ulUnicodeRange2"]); offset += 4; + bin.writeUint(buff, offset, obj["ulUnicodeRange3"]); offset += 4; + bin.writeUint(buff, offset, obj["ulUnicodeRange4"]); offset += 4; + bin.writeASCII(buff, offset, obj["achVendID"]); offset += 4; + bin.writeUint16(buff, offset, obj["fsSelection"]); offset += 2; + bin.writeUint16(buff, offset, obj["usFirstCharIndex"]); offset += 2; + bin.writeUint16(buff, offset, obj["usLastCharIndex"]); offset += 2; + bin.writeShort(buff, offset, obj["sTypoAscender"]); offset += 2; + bin.writeShort(buff, offset, obj["sTypoDescender"]); offset += 2; + bin.writeShort(buff, offset, obj["sTypoLineGap"]); offset += 2; + bin.writeUint16(buff, offset, obj["usWinAscent"]); offset += 2; + bin.writeUint16(buff, offset, obj["usWinDescent"]); offset += 2; + + if (version >= 1) { + bin.writeUint(buff, offset, obj["ulCodePageRange1"]); offset += 4; + bin.writeUint(buff, offset, obj["ulCodePageRange2"]); offset += 4; + } + + if (version >= 2) { + bin.writeShort(buff, offset, obj["sxHeight"]); offset += 2; + bin.writeShort(buff, offset, obj["sCapHeight"]); offset += 2; + bin.writeUint16(buff, offset, obj["usDefault"]); offset += 2; + bin.writeUint16(buff, offset, obj["usBreak"]); offset += 2; + bin.writeUint16(buff, offset, obj["usMaxContext"]); offset += 2; + } + + if (version >= 5) { + bin.writeUint16(buff, offset, obj["usLowerOpticalPointSize"]); offset += 2; + bin.writeUint16(buff, offset, obj["usUpperOpticalPointSize"]); + } + } } +Untypr["T"].post = { + encodeTab: function(obj, offset, buff) { + var bin = Untypr["B"]; + // We don't have the PS name information needed for v2, so we're storing as v3 only + bin.writeUint(buff, offset, 0x00030000); offset += 4; + bin.writeFixed(buff, offset, obj["italicAngle"]); offset += 4; + bin.writeShort(buff, offset, obj["underlinePosition"]); offset +=2; + bin.writeShort(buff, offset, obj["underlineThickness"]); offset += 2; + // FIXME: Typr doesn't load "isFixedPitch" + bin.writeUint(buff, offset, 0); offset += 4; + bin.writeUint(buff, offset, 0); offset += 4; // minMemType42 + bin.writeUint(buff, offset, 0); offset += 4; // maxMemType42 + bin.writeUint(buff, offset, 0); offset += 4; // minMemType1 + bin.writeUint(buff, offset, 0); // maxMemType1 + } +} Untypr["B"] = { writeUint: function(buff, p, n) @@ -49,8 +275,8 @@ Untypr["B"] = { }, writeInt: function(buff, p, n) { - var a = Typr["B"].t.uint8; - Typr["B"].t.int32[0] = n; + var a = Untypr["B"].t.uint8; + Untypr["B"].t.int32[0] = n; buff[p] = a[3]; buff[p+1] = a[2]; buff[p+2] = a[1]; @@ -58,15 +284,15 @@ Untypr["B"] = { }, writeShort: function(buff, p, n) { - var a = Typr["B"].t.uint8; - Typr["B"].t.int16[0] = n; + var a = Untypr["B"].t.uint8; + Untypr["B"].t.int16[0] = n; buff[p] = a[1]; buff[p+1] = a[0]; }, writeInt8: function(buff, p, n) { - var a = Typr["B"].t.uint8; - Typr["B"].t.int8[0] = n; + var a = Untypr["B"].t.uint8; + Untypr["B"].t.int8[0] = n; buff[p] = a[0]; }, writeInt64: function(buff, p, n) @@ -75,7 +301,7 @@ Untypr["B"] = { }, writeF2dot14: function(buff, p, n) { - Typr["B"].writeShort(buff, p, n * 16384) + Untypr["B"].writeShort(buff, p, n * 16384) }, writeFixed: function(buff, p, n) { @@ -85,6 +311,16 @@ Untypr["B"] = { { for(var i=0; i Date: Mon, 6 Sep 2021 16:15:15 +0200 Subject: [PATCH 03/10] changed write method signature --- src/Untypr.js | 234 ++++++++++++++++++++++++++++---------------------- 1 file changed, 130 insertions(+), 104 deletions(-) diff --git a/src/Untypr.js b/src/Untypr.js index ae06f63..87f8990 100644 --- a/src/Untypr.js +++ b/src/Untypr.js @@ -4,37 +4,37 @@ var Untypr = {}; Untypr["T"] = {}; Untypr["T"].head = { - encodeTab: function(obj, offset, buff) + encodeTab: function(obj) { var bin = Untypr["B"]; - bin.writeUint16(buff, offset, 1); offset += 2; // majorVersion - bin.writeUint16(buff, offset, 0); offset += 2; // minorVersion - bin.writeFixed(buff, offset, obj["fontRevision"]); offset += 4; - var checkSumOffset = offset; // TODO - bin.writeUint(buff, offset, 0); offset += 4; // checkSumAdjustment - bin.writeUint(buff, offset, 0x5f0f3cf5); offset += 4; // magic constant - bin.writeUint(buff, offset, obj["flags"]); offset += 2; - bin.writeUshort(buff, offset, obj["unitsPerEm"]); offset += 2; - bin.writeUint64(buff, offset, obj["created"]); offset += 8; - bin.writeUint64(buff, offset, obj["modified"]); offset += 8; - bin.writeShort(buff, offset, obj["xMin"]); offset += 2; - bin.writeShort(buff, offset, obj["yMin"]); offset += 2; - bin.writeShort(buff, offset, obj["xMax"]); offset += 2; - bin.writeShort(buff, offset, obj["yMax"]); offset += 2; - bin.writeUint16(buff, offset, obj["macStyle"]); offset += 2; - bin.writeUint16(buff, offset, obj["lowestRecPPEM"]); offset += 2; - bin.writeShort(buff, offset, obj["fontDirectionHint"]); offset += 2; - bin.writeShort(buff, offset, obj["indexToLocFormat"]); offset += 2; - bin.writeShort(buff, offset, obj["glyphDataFormat"]); + bin.writeUint16(1); // majorVersion + bin.writeUint16(0); // minorVersion + bin.writeFixed(obj["fontRevision"]); + var checkSumOffset = bin.getCurrentOffset(); // TODO + bin.writeUint(0); // checkSumAdjustment + bin.writeUint(0x5f0f3cf5); // magic constant + bin.writeUint(obj["flags"]); + bin.writeUshort(obj["unitsPerEm"]); + bin.writeUint64(obj["created"]); + bin.writeUint64(obj["modified"]); + bin.writeShort(obj["xMin"]); + bin.writeShort(obj["yMin"]); + bin.writeShort(obj["xMax"]); + bin.writeShort(obj["yMax"]); + bin.writeUint16(obj["macStyle"]); + bin.writeUint16(obj["lowestRecPPEM"]); + bin.writeShort(obj["fontDirectionHint"]); + bin.writeShort(obj["indexToLocFormat"]); + bin.writeShort(obj["glyphDataFormat"]); } }; Untypr["T"].hhea = { - encodeTab: function(obj, offset, buff) + encodeTab: function(obj) { var bin = Untypr["B"]; - bin.writeUint16(buff, offset, 1); offset += 2;// major version; - bin.writeUint16(buff, offset, 0); offset += 2;// minor version; + bin.writeUint16(1); // major version; + bin.writeUint16(0); // minor version; var keys = ["ascender","descender","lineGap", "advanceWidthMax","minLeftSideBearing","minRightSideBearing","xMaxExtent", @@ -44,42 +44,42 @@ Untypr["T"].hhea = { for(var i=0; i= 1) { - bin.writeUint(buff, offset, obj["ulCodePageRange1"]); offset += 4; - bin.writeUint(buff, offset, obj["ulCodePageRange2"]); offset += 4; + bin.writeUint(obj["ulCodePageRange1"]); + bin.writeUint(obj["ulCodePageRange2"]); } if (version >= 2) { - bin.writeShort(buff, offset, obj["sxHeight"]); offset += 2; - bin.writeShort(buff, offset, obj["sCapHeight"]); offset += 2; - bin.writeUint16(buff, offset, obj["usDefault"]); offset += 2; - bin.writeUint16(buff, offset, obj["usBreak"]); offset += 2; - bin.writeUint16(buff, offset, obj["usMaxContext"]); offset += 2; + bin.writeShort(obj["sxHeight"]); + bin.writeShort(obj["sCapHeight"]); + bin.writeUint16(obj["usDefault"]); + bin.writeUint16(obj["usBreak"]); + bin.writeUint16(obj["usMaxContext"]); } if (version >= 5) { - bin.writeUint16(buff, offset, obj["usLowerOpticalPointSize"]); offset += 2; - bin.writeUint16(buff, offset, obj["usUpperOpticalPointSize"]); + bin.writeUint16(obj["usLowerOpticalPointSize"]); + bin.writeUint16(obj["usUpperOpticalPointSize"]); } } } @@ -257,17 +257,21 @@ Untypr["T"].post = { } Untypr["B"] = { - writeUint: function(buff, p, n) + writeUint: function(n) { - buff[p] = (n>>24)&255; buff[p+1] = (n>>16)&255; buff[p+2] = (n>>8)&255; buff[p+3] = n&255; + this._out.push((n>>24)&255); + this._out.push((n>>16)&255); + this._out.push((n>>8)&255); + this._out.push(n&255); }, - writeUint16: function(buff, p, n) + writeUint16: function(n) { - buff[p] = (n>>8)&255; buff[p+1] = n&255; + this._out.push((n>>8)&255); + this._out.push(n&255); }, - writeUint8: function(buff, p, n) + writeUint8: function(n) { - buff[p] = n&255; + this._out.push(n&255); }, writeUint64: function(buff, p, n) { @@ -277,23 +281,23 @@ Untypr["B"] = { { var a = Untypr["B"].t.uint8; Untypr["B"].t.int32[0] = n; - buff[p] = a[3]; - buff[p+1] = a[2]; - buff[p+2] = a[1]; - buff[p+3] = a[0]; + this._out.push(a[3]); + this._out.push(a[2]); + this._out.push(a[1]); + this._out.push(a[0]); }, writeShort: function(buff, p, n) { var a = Untypr["B"].t.uint8; Untypr["B"].t.int16[0] = n; - buff[p] = a[1]; - buff[p+1] = a[0]; + this._out.push(a[1]); + this._out.push(a[0]); }, writeInt8: function(buff, p, n) { var a = Untypr["B"].t.uint8; Untypr["B"].t.int8[0] = n; - buff[p] = a[0]; + this._out.push(a[0]); }, writeInt64: function(buff, p, n) { @@ -307,19 +311,19 @@ Untypr["B"] = { { // TODO }, - writeBytes: function(buff, p, arr) + writeBytes: function(arr) { - for(var i=0; i out.offset) out.offset = offset; + if (offset >= out.arr.length) { + var ab = new ArrayBuffer(arr.length * 2); + var newArr = new Uint8Array(ab); + newArr.set(out.arr); + out.arr = newArr; + } + out.arr[offset] = byte; + }, + push: function(byte) { + var out = Untypr["B"]._out; + out.write(byte, out.offset + 1); + } + }, + getCurrentOffset: function() { + return Untypr["B"]._out.offset; + } }; From c1f82cfe5e2898690964d49ef134082845f72688 Mon Sep 17 00:00:00 2001 From: Vaclav Lunak Date: Tue, 7 Sep 2021 15:43:41 +0200 Subject: [PATCH 04/10] added cmap --- src/Untypr.js | 101 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 91 insertions(+), 10 deletions(-) diff --git a/src/Untypr.js b/src/Untypr.js index 87f8990..6436114 100644 --- a/src/Untypr.js +++ b/src/Untypr.js @@ -94,6 +94,7 @@ Untypr["T"].maxp = { }; Untypr["T"].name = { + // FIXME: wrong obj format used encodeTab: function(obj) { var bin = Untypr["B"]; @@ -240,22 +241,102 @@ Untypr["T"].OS2 = { } Untypr["T"].post = { - encodeTab: function(obj, offset, buff) { + encodeTab: function(obj) { var bin = Untypr["B"]; // We don't have the PS name information needed for v2, so we're storing as v3 only - bin.writeUint(buff, offset, 0x00030000); offset += 4; - bin.writeFixed(buff, offset, obj["italicAngle"]); offset += 4; - bin.writeShort(buff, offset, obj["underlinePosition"]); offset +=2; - bin.writeShort(buff, offset, obj["underlineThickness"]); offset += 2; + bin.writeUint(0x00030000); + bin.writeFixed(obj["italicAngle"]); + bin.writeShort(obj["underlinePosition"]); + bin.writeShort(obj["underlineThickness"]); // FIXME: Typr doesn't load "isFixedPitch" - bin.writeUint(buff, offset, 0); offset += 4; - bin.writeUint(buff, offset, 0); offset += 4; // minMemType42 - bin.writeUint(buff, offset, 0); offset += 4; // maxMemType42 - bin.writeUint(buff, offset, 0); offset += 4; // minMemType1 - bin.writeUint(buff, offset, 0); // maxMemType1 + bin.writeUint(0); // isFixedPitch + bin.writeUint(0); // minMemType42 + bin.writeUint(0); // maxMemType42 + bin.writeUint(0); // minMemType1 + bin.writeUint(0); // maxMemType1 } } +Untypr["T"].cmap = { + encodeTab: function(obj) { + var bin = Untypr["B"]; + bin.writeUint16(0); // version + bin.writeUint16(obj.tables.length); // numTables + + for (var key in Object.keys(obj.ids)) { + var match = key.match(/^p(?[0-9]+)e(?[0-9]+)$/); + if (!match) continue; + bin.writeUint16(match.platformID); + bin.writeUint16(match.encodingID); + bin.writeUint(0); // TODO subtableOffset + } + + for (var subt of obj.tables) { + if (subt.format == 0) this.encode0(subt); + else if (subt.format == 4) this.encode4(subt); + else if (subt.format == 6) this.encode6(subt); + else if (subt.format == 12) this.encode12(subt); + // else console.log(`unknown subtable format ${subt.format}`); + } + }, + + encode0: function(table) { + var bin = Untypr["B"]; + bin.writeUint16(0); // format + bin.writeUint16(table.map.length + 6); // length + bin.writeUint16(0) // language, FIXME: we assume not language-specific on Mac + for (var glyphId of table.map) { + bin.writeUint8(glyphId); + } + }, + encode4: function(table) { + var bin = Untypr["B"]; + bin.writeUint16(4); // format + bin.writeUint16(0); // TODO length + bin.writeUint16(0); // language, FIXME: we assume not language-specific on Mac + bin.writeUint16(table.startCount * 2); // segCountX2 + bin.writeUint16(table.searchRange); + bin.writeUint16(table.entrySelector); + bin.writeUint16(table.rangeShift); + for (var endCount of table.endCount) { + bin.writeUint16(endCount); + } + bin.writeUint16(0); // reservedPad + for (var startCount of table.startCount) { + bin.writeUint16(startCount); + } + for (var idDelta of table.idDelta) { + bin.writeUint16(idDelta); + } + for (var idRangeOffset of table.idRangeOffset) { + bin.writeUint16(idRangeOffset); + } + for (var glyphID of table.glyphIdArray) { + bin.writeUint16(glyphID); + } + }, + encode6: function(table) { + var bin = Untypr["B"]; + bin.writeUint16(6); // format + bin.writeUint16(0); // TODO length + bin.writeUint16(0); // FIXME assumes language-independent encoding + bin.writeUint16(table.firstCode); + bin.writeUint16(table.glyphIdArray.length); // entryCount + for (var id of table.glyphIdArray) bin.writeUint16(id); + }, + encode12: function(table) { + var bin = Untypr["B"]; + bin.writeUint16(12); // format + bin.writeUint16(0); // reserved + bin.writeUint(0); // TODO length + bin.writeUint(0); // language + bin.writeUint(table.groups.length / 3); // numGroups + for (var group of table.groups) { + bin.writeUint(group); + } + }, +} + Untypr["B"] = { writeUint: function(n) { From c061a8c2acfc5705a273b08f5fc5388652b2793b Mon Sep 17 00:00:00 2001 From: Vaclav Lunak Date: Tue, 7 Sep 2021 18:40:16 +0200 Subject: [PATCH 05/10] loca, kern, glyf, SVG --- src/Untypr.js | 214 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) diff --git a/src/Untypr.js b/src/Untypr.js index 6436114..653cf98 100644 --- a/src/Untypr.js +++ b/src/Untypr.js @@ -337,6 +337,220 @@ Untypr["T"].cmap = { }, } +Untypr["T"].loca = { + encodeTab: function(obj, font) { + var bin = Typr["B"]; + var version = font["head"]["indexToLocFormat"]; + if (version == 0) for (var short of obj) bin.writeUint16(short >> 1); + if (version == 1) for (var long of obj) bin.writeUint(long); + } +} + +Untypr["T"].kern = { + encodeTab: function(obj) { + var bin = Typr["B"]; + var nPairs = obj.rval.reduceRight((n, o) => n + o.vals.length, 0); + bin.writeUint16(0); // version + // FIXME Typr combines all subtables into one map, so we just write one subtable + bin.writeUint16(obj.glyph1.length > 0 ? 1 : 0); + + bin.writeUint16(0); // subtable version + bin.writeUint16(0); // TODO length + // FIXME Typr doesn't store coverage data for kerning subtables + bin.writeUint16(0x0000); // TODO coverage + bin.writeUint16(nPairs); + var searchRange = 1; + var entrySelector = 0; + while (searchRange < nPairs) + { + searchRange <<= 1; + entrySelector += 1; + } + searchRange >>= 1; entrySelector -= 1; + var rangeShift = nPairs - searchRange; + bin.writeUint16(searchRange * 6); + bin.writeUint16(entrySelector); + bin.writeUint16(rangeShift * 6); + for (var i = 0; i < obj.glyph1.length; i++) { + var g1 = obj.glyph1[i]; + var rval = obj.rval[i]; + for (var j = 0; j < rval.glyph2.length; j++) { + bin.writeUint16(g1); + bin.writeUint16(rval.glyph2[j]); + bin.writeShort(rval.vals[j]); + } + } + } +} + +Untypr["T"].glyf = { + encodeTab: function(obj) { + var bin = Untypr["B"]; + for (var glyph of obj) { + bin.writeShort(glyph.noc); + bin.writeShort(glyph.xMin); + bin.writeShort(glyph.yMin); + bin.writeShort(glyph.xMax); + bin.writeShort(glyph.yMax); + if (glyph.noc >= 0) this.encodeSimpleGlyph(glyph); + else this.encodeCompositeGlyph(glyph); + } + }, + encodeSimpleGlyph: function(glyph) { + var bin = Untypr["B"]; + for (var endPt of glyph.endPts) { + bin.writeUint16(endPt); + } + bin.writeUint16(glyph.instructions.length); + for (var inst of glyph.instructions) { + bin.writeUint8(inst); + } + for (var flag of glyph.flags) { + // the flags are deduplicated, so we must unset the REPEAT bit + bin.writeUint8(flag & 0b11110111); + } + var i; var lastI = 0; var isShort, isSame; + for (i = 0; i < glyph.xs.length; i++) { + isShort = glyph.flags[i] & 0x02; + isSame = glyph.flags[i] & 0x10; + if (isShort) { + bin.writeUint8(isSame ? glyph.xs[i] : -glyph.xs[i]); + } else { + if (!isSame) lastI = i; + bin.writeShort(glyph.xs[lastI]); + } + } + + lastI = 0; + for (i = 0; i < glyph.ys.length; i++) { + isShort = glyph.flags[i] & 0x04; + isSame = glyph.flags[i] & 0x20; + if (isShort) { + bin.writeUint8(isSame ? glyph.ys[i] : -glyph.ys[i]); + } else { + if (!isSame) lastI = i; + bin.writeShort(glyph.ys[lastI]); + } + } + }, + encodeCompositeGlyph: function(glyph) { + var bin = Untypr["B"]; + var ARG_1_AND_2_ARE_WORDS = 1<<0; + var ARGS_ARE_XY_VALUES = 1<<1; + var ROUND_XY_TO_GRID = 1<<2; + var WE_HAVE_A_SCALE = 1<<3; + var RESERVED = 1<<4; + var MORE_COMPONENTS = 1<<5; + var WE_HAVE_AN_X_AND_Y_SCALE= 1<<6; + var WE_HAVE_A_TWO_BY_TWO = 1<<7; + var WE_HAVE_INSTRUCTIONS = 1<<8; + + for (var i = 0; i < glyph.parts.length; i++) { + var part = glyph.parts[i]; + // determine flags and arguments + var flags = 0; + var arg1, arg2; + if (part.p1 == -1 && part.p2 == -1) { + arg1 = part.m.tx; arg2 = part.m.ty; + flags |= ARGS_ARE_XY_VALUES; + if (arg1 >= 128 || arg1 < -128 || arg2 >= 128 || arg2 < -128) + flags |= ARG_1_AND_2_ARE_WORDS; + } else { + arg1 = part.p1; arg2 = part.p2; + if (arg1 > 256 || arg2 > 256) + flags |= ARG_1_AND_2_ARE_WORDS; + } + + if (part.m.a == 1 && part.m.b == 0 && part.m.c == 0 && part.m.d == 1) + flags |= 0; + else if (part.m.a == part.m.d && part.m.b == 0 && part.m.c == 0) + flags |= WE_HAVE_A_SCALE; + else if (part.m.b == 0 && part.m.c == 0) + flags |= WE_HAVE_AN_X_AND_Y_SCALE; + else + flags |= WE_HAVE_A_TWO_BY_TWO; + + if (i == glyph.parts.length - 1) { + if (glyph.instr.length > 0) + flags |= WE_HAVE_INSTRUCTIONS; + } else { + flags |= MORE_COMPONENTS; + } + + // write glyph component + bin.writeUint16(flags); + bin.writeUint16(part.glyphIndex); + if (flags & ARG_1_AND_2_ARE_WORDS) { + bin.writeUint16(arg1); + bin.writeUint16(arg2); + } else { + bin.writeUint16((arg1 << 8) | arg2); + } + + if (flags & WE_HAVE_A_SCALE) { + bin.writeF2dot14(part.m.a); + } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) { + bin.writeF2dot14(part.m.a); + bin.writeF2dot14(part.m.d); + } else if (flags & WE_HAVE_A_TWO_BY_TWO) { + bin.writeF2dot14(part.m.a); + bin.writeF2dot14(part.m.b); + bin.writeF2dot14(part.m.c); + bin.writeF2dot14(part.m.d); + } + } + + if (glyph.instr.length > 0) { + bin.writeUint16(glyph.instr.length); + for (var instr of glyph.instr) { + bin.writeUint8(instr); + } + } + } +} + +Untypr["T"].SVG = { + encodeTab: function(obj) { + var bin = Untypr["B"]; + bin.writeUint16(0); // version + bin.writeUint(10); // svgDocumentListOffset + bin.writeUint(0); // reserved + + // convert entries array into ranges + var ranges = []; + var svgs = []; + var currStart = -1; + var currSvg; + for (var i = 0; i < obj.entries.length; i++) { + if (typeof currSvg == "undefined" && typeof obj.entries[i] == "undefined") { + continue; + } + if (typeof currSvg == "undefined") { + currStart = i; + currSvg = obj.entries[i]; + } + if (obj.entries[i] != currSvg) { + ranges.push([currStart, i-1]); + svgs.push(currSvg); + currSvg = obj.entries[i]; + currStart = i; + } + } + + bin.writeUint16(ranges.length); // numEntries + for (var range of ranges) { + bin.writeUint16(range[0]); // startGlyphID + bin.writeUint16(range[1]); // endGlyphID + bin.writeUint(0); // TODO svgDocOffset + bin.writeUint(0); // TODO svgDocLength + } + + for (var svg of svgs) { + bin.writeUnicode(svg); + } + } +} + Untypr["B"] = { writeUint: function(n) { From 87377f00d9e0ee8b2d2b0eb756de9ba9904d51a6 Mon Sep 17 00:00:00 2001 From: Vaclav Lunak Date: Wed, 8 Sep 2021 16:10:09 +0200 Subject: [PATCH 06/10] fixes, offsets, glue --- src/Untypr.js | 238 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 169 insertions(+), 69 deletions(-) diff --git a/src/Untypr.js b/src/Untypr.js index 653cf98..c6b214b 100644 --- a/src/Untypr.js +++ b/src/Untypr.js @@ -1,5 +1,86 @@ var Untypr = {}; +Untypr["encode"] = function(font) { + var encoders = [ + "head", "cmap", "hhea", "maxp", "hmtx", "name", + "OS/2", "post", "loca", "kern", "glyf", "SVG" + ]; + var toEncode = []; + var bin = Untypr["B"]; + bin._out.clear(); + var numTables = 0; + for (var enc of encoders) { + if (font.hasOwnProperty(enc)) { + numTables++; + toEncode.push(enc); + } + } + var searchRange = 1; + var entrySelector = 0; + while (searchRange < numTables) { + searchRange <<= 1; + entrySelector += 1; + } + searchRange <<= 3; // /2 * 16 + entrySelector -= 1; + var rangeShift = (numTables << 4) - searchRange; + + // table directory + bin.writeUint(0x00010000); // sfntVersion TODO CFF? + bin.writeUint16(numTables); + bin.writeUint16(searchRange); + bin.writeUint16(entrySelector); + bin.writeUint16(rangeShift); + + // table records + var tableRecordOffsets = {}; + for (var tableTag of toEncode) { + tableRecordOffsets[tableTag] = bin.getCurrentOffset(); + bin.writeASCII(tableTag); + bin.writeUint(0); // checksum + bin.writeUint(0); // offset + bin.writeUint(0); // length + } + + // tables themselves + var tableOffsets = {} + for (var tableTag of toEncode) { + var tableStartOffset = bin.getCurrentOffset(); + Untypr["T"][tableTag].encodeTab(font[tableTag]); + var tableEndOffset = bin.getCurrentOffset(); + tableOffsets[tableTag] = { start: tableStartOffset, end: tableEndOffset }; + while (bin.getCurrentOffset() % 4 != 0) { + // tables must be aligned to 4 byte boundaries + bin.writeUint8(0); + } + } + + // compute record fields + for (var tableTag of toEncode) { + var off = tableRecordOffsets[tableTag]; + var lim = tableOffsets[tableTag]; + var checksum = Untypr["getChecksum"](lim.start, lim.end); + bin.writeUint(checksum, off+4); + bin.writeUint(lim.start, off+8); + bin.writeUint(lim.end - lim.start, off+12); + } + + // compute total font checksum and write adjustment to "head" + var totalChksm = Untypr["getChecksum"](0, bin.getCurrentOffset()); + var chksmAdjust = 0xb1b0afba - totalChksm; + bin.writeUint(chksmAdjust, tableOffsets["head"].start + 8); + + return bin._out.arr.buffer; +} + +Untypr["getChecksum"] = function(start, end) { + while (end % 4 != 0) end++; + var sum = 0n; + for (var i = start; i < end; i += 4) { + sum += Untypr["B"].readUint(i); + } + return sum & 0xffffffff; +} Untypr["T"] = {}; @@ -10,7 +91,6 @@ Untypr["T"].head = { bin.writeUint16(1); // majorVersion bin.writeUint16(0); // minorVersion bin.writeFixed(obj["fontRevision"]); - var checkSumOffset = bin.getCurrentOffset(); // TODO bin.writeUint(0); // checkSumAdjustment bin.writeUint(0x5f0f3cf5); // magic constant bin.writeUint(obj["flags"]); @@ -99,9 +179,10 @@ Untypr["T"].name = { { var bin = Untypr["B"]; // Typr doesn't support v1, so store only v0 + var startOffset = bin.getCurrentOffset(); bin.writeShort(0); bin.writeShort(obj["count"]); - bin.writeShort(0); // TODO: add offset + bin.writeShort(0); // storageOffset var names = [ "copyright", @@ -139,10 +220,10 @@ Untypr["T"].name = { var pID = Number.parseInt(match.platformID, 10); var lID = Number.parseInt(match.languageID, 16); // FIXME: We just use Unicode or pick Roman and hope - var encID; - if (pID == 0) encID = 4; // Unicode, full Unicode - else if (pID == 1) encID = 0; // Macintosh, Roman - else if (pID == 3) encID = 10; // Windows, full Unicode + var eID; + if (pID == 0) eID = 4; // Unicode, full Unicode + else if (pID == 1) eID = 0; // Macintosh, Roman + else if (pID == 3) eID = 10; // Windows, full Unicode for (var name in Object.keys(obj[key])) { var nID = names.indexOf(name); @@ -163,18 +244,25 @@ Untypr["T"].name = { return 0; }); + var offsets = []; for (var r of records) { bin.writeUint16(r.pID); bin.writeUint16(r.eID); bin.writeUint16(r.lID); bin.writeUint16(r.nID); bin.writeUint16(obj[r.key][names[r.nID]].length); - bin.writeUint16(0); // TODO add offsets; + offsets.push(bin.getCurrentOffset()); + bin.writeUint16(0); // offset; } - for (r of records) { - var func = (r.pID == 1 && r.eID == 0) ? bin.writeASCII : bin.writeUnicode; - func(obj[r.key][names[r.nID]]); + var dataOffset = bin.getCurrentOffset(); + bin.writeUint16(dataOffset - startOffset, startOffset); + + for (var i = 0; i < records.length; i++) { + var rec = records[i]; + var func = (rec.pID == 1 && rec.eID == 0) ? bin.writeASCII : bin.writeUnicode; + bin.writeUint16(bin.getCurrentOffset() - startOffset, offsets[i]); + func(obj[rec.key][names[rec.nID]]); } } } @@ -260,6 +348,7 @@ Untypr["T"].post = { Untypr["T"].cmap = { encodeTab: function(obj) { var bin = Untypr["B"]; + var startOffset = bin.getCurrentOffset(); bin.writeUint16(0); // version bin.writeUint16(obj.tables.length); // numTables @@ -292,9 +381,11 @@ Untypr["T"].cmap = { encode4: function(table) { var bin = Untypr["B"]; bin.writeUint16(4); // format - bin.writeUint16(0); // TODO length + var segCount = table.startCount.length; + var length = (8 + (segCount<<2) + table.glyphIdArray.length) << 1; + bin.writeUint16(length); bin.writeUint16(0); // language, FIXME: we assume not language-specific on Mac - bin.writeUint16(table.startCount * 2); // segCountX2 + bin.writeUint16(segCount<<1); // segCountX2 bin.writeUint16(table.searchRange); bin.writeUint16(table.entrySelector); bin.writeUint16(table.rangeShift); @@ -318,7 +409,8 @@ Untypr["T"].cmap = { encode6: function(table) { var bin = Untypr["B"]; bin.writeUint16(6); // format - bin.writeUint16(0); // TODO length + var length = (table.glyphIdArray.length + 5)<<1; + bin.writeUint16(length); bin.writeUint16(0); // FIXME assumes language-independent encoding bin.writeUint16(table.firstCode); bin.writeUint16(table.glyphIdArray.length); // entryCount @@ -328,7 +420,8 @@ Untypr["T"].cmap = { var bin = Untypr["B"]; bin.writeUint16(12); // format bin.writeUint16(0); // reserved - bin.writeUint(0); // TODO length + var length = 16 + (table.groups.length<<2); + bin.writeUint(length); bin.writeUint(0); // language bin.writeUint(table.groups.length / 3); // numGroups for (var group of table.groups) { @@ -355,7 +448,8 @@ Untypr["T"].kern = { bin.writeUint16(obj.glyph1.length > 0 ? 1 : 0); bin.writeUint16(0); // subtable version - bin.writeUint16(0); // TODO length + var length = 6 * nPairs + 14; + bin.writeUint16(length); // FIXME Typr doesn't store coverage data for kerning subtables bin.writeUint16(0x0000); // TODO coverage bin.writeUint16(nPairs); @@ -437,9 +531,7 @@ Untypr["T"].glyf = { var bin = Untypr["B"]; var ARG_1_AND_2_ARE_WORDS = 1<<0; var ARGS_ARE_XY_VALUES = 1<<1; - var ROUND_XY_TO_GRID = 1<<2; var WE_HAVE_A_SCALE = 1<<3; - var RESERVED = 1<<4; var MORE_COMPONENTS = 1<<5; var WE_HAVE_AN_X_AND_Y_SCALE= 1<<6; var WE_HAVE_A_TWO_BY_TWO = 1<<7; @@ -537,88 +629,94 @@ Untypr["T"].SVG = { } } + var documentListOffset = bin.getCurrentOffset(); + var offsets = []; bin.writeUint16(ranges.length); // numEntries for (var range of ranges) { bin.writeUint16(range[0]); // startGlyphID bin.writeUint16(range[1]); // endGlyphID - bin.writeUint(0); // TODO svgDocOffset - bin.writeUint(0); // TODO svgDocLength + offsets.push(bin.getCurrentOffset()); + bin.writeUint(0); // svgDocOffset + bin.writeUint(0); // svgDocLength } - for (var svg of svgs) { - bin.writeUnicode(svg); + for (var i = 0; i < svgs.length; i++) { + var startDocOffset = bin.getCurrentOffset(); + bin.writeUnicode(svgs[i]); + var endDocOffset = bin.getCurrentOffset(); + bin.writeUint(startDocOffset - documentListOffset, offsets[i]); + bin.writeUint(endDocOffset - startDocOffset, offsets[i] + 4); } } } Untypr["B"] = { - writeUint: function(n) + readUint : function(p) { - this._out.push((n>>24)&255); - this._out.push((n>>16)&255); - this._out.push((n>>8)&255); - this._out.push(n&255); + //if(p>=buff.length) throw "error"; + var buff = this._out.arr; + var a = Untypr["B"].t.uint8; + a[3] = buff[p]; a[2] = buff[p+1]; a[1] = buff[p+2]; a[0] = buff[p+3]; + return Untypr["B"].t.uint32[0]; }, - writeUint16: function(n) + writeUint: function(n, p) { - this._out.push((n>>8)&255); - this._out.push(n&255); + this._out.write((n>>24)&255, p); + this._out.write((n>>16)&255, p+1); + this._out.write((n>>8)&255, p+2); + this._out.write(n&255, p+3); }, - writeUint8: function(n) + writeUint16: function(n, p) { - this._out.push(n&255); + this._out.write((n>>8)&255, p); + this._out.write(n&255, p+1); }, - writeUint64: function(buff, p, n) + writeUint8: function(n, p) { - // TODO + this._out.write(n&255, p); }, - writeInt: function(buff, p, n) + writeUint64: function(n, p) { - var a = Untypr["B"].t.uint8; - Untypr["B"].t.int32[0] = n; - this._out.push(a[3]); - this._out.push(a[2]); - this._out.push(a[1]); - this._out.push(a[0]); + Untypr["B"].writeUint(BigInt(n) >> BigInt(32), p); + Untypr["B"].writeUint(n & 0xffffffff, p+1); }, - writeShort: function(buff, p, n) + writeInt: function(n, p) { var a = Untypr["B"].t.uint8; - Untypr["B"].t.int16[0] = n; - this._out.push(a[1]); - this._out.push(a[0]); + Untypr["B"].t.int32[0] = n; + this._out.write(a[3], p); + this._out.write(a[2], p+1); + this._out.write(a[1], p+2); + this._out.write(a[0], p+3); }, - writeInt8: function(buff, p, n) + writeShort: function(n, p) { var a = Untypr["B"].t.uint8; - Untypr["B"].t.int8[0] = n; - this._out.push(a[0]); - }, - writeInt64: function(buff, p, n) - { - // TODO + Untypr["B"].t.int16[0] = n; + this._out.write(a[1], p); + this._out.write(a[0], p+1); }, - writeF2dot14: function(buff, p, n) + writeF2dot14: function(n, p) { - Untypr["B"].writeShort(buff, p, n * 16384) + Untypr["B"].writeShort(n * 16384, p) }, - writeFixed: function(buff, p, n) + writeFixed: function(n, p) { - // TODO + Untypr["B"].writeInt(n * 65536, p); }, - writeBytes: function(arr) + writeBytes: function(arr, p) { - for(var i=0; i out.offset) out.offset = offset; - if (offset >= out.arr.length) { - var ab = new ArrayBuffer(arr.length * 2); + if (!Number.isFinite(offset)) offset = out.length; + if (offset >= out.length) out.length = offset + 1; + while (out.length > out.arr.buffer.length) { + var ab = new ArrayBuffer(out.arr.buffer.length * 2); var newArr = new Uint8Array(ab); newArr.set(out.arr); out.arr = newArr; } out.arr[offset] = byte; }, - push: function(byte) { + clear: function() { var out = Untypr["B"]._out; - out.write(byte, out.offset + 1); + out.arr = new Uint8Array(1000); + out.length = 0; } }, getCurrentOffset: function() { - return Untypr["B"]._out.offset; + return Untypr["B"]._out.length; } }; From 0efb24c46f95e08e185a9fba2eeab2d631168689 Mon Sep 17 00:00:00 2001 From: Vaclav Lunak Date: Wed, 15 Sep 2021 17:46:14 +0200 Subject: [PATCH 07/10] fixes --- demo/index.html | 12 ++- src/Untypr.js | 268 ++++++++++++++++++++++++++---------------------- 2 files changed, 156 insertions(+), 124 deletions(-) diff --git a/demo/index.html b/demo/index.html index dbe1b7b..5a0b0d2 100644 --- a/demo/index.html +++ b/demo/index.html @@ -54,7 +54,7 @@ - +