| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704 |
- /* Copyright 2021 Mozilla Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- const ON_CURVE_POINT = 1 << 0;
- const X_SHORT_VECTOR = 1 << 1;
- const Y_SHORT_VECTOR = 1 << 2;
- const REPEAT_FLAG = 1 << 3;
- const X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR = 1 << 4;
- const Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR = 1 << 5;
- const OVERLAP_SIMPLE = 1 << 6;
- const ARG_1_AND_2_ARE_WORDS = 1 << 0;
- const ARGS_ARE_XY_VALUES = 1 << 1;
- // const ROUND_XY_TO_GRID = 1 << 2;
- const WE_HAVE_A_SCALE = 1 << 3;
- const MORE_COMPONENTS = 1 << 5;
- const WE_HAVE_AN_X_AND_Y_SCALE = 1 << 6;
- const WE_HAVE_A_TWO_BY_TWO = 1 << 7;
- const WE_HAVE_INSTRUCTIONS = 1 << 8;
- // const USE_MY_METRICS = 1 << 9;
- // const OVERLAP_COMPOUND = 1 << 10;
- // const SCALED_COMPONENT_OFFSET = 1 << 11;
- // const UNSCALED_COMPONENT_OFFSET = 1 << 12;
- /**
- * GlyfTable object represents a glyf table containing glyph information:
- * - glyph header (xMin, yMin, xMax, yMax);
- * - contours if any;
- * - components if the glyph is a composite.
- *
- * It's possible to re-scale each glyph in order to have a new font which
- * exactly fits an other one: the goal is to be able to build some substitution
- * font for well-known fonts (Myriad, Arial, ...).
- *
- * A full description of glyf table can be found here
- * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6glyf.html
- */
- class GlyfTable {
- constructor({ glyfTable, isGlyphLocationsLong, locaTable, numGlyphs }) {
- this.glyphs = [];
- const loca = new DataView(
- locaTable.buffer,
- locaTable.byteOffset,
- locaTable.byteLength
- );
- const glyf = new DataView(
- glyfTable.buffer,
- glyfTable.byteOffset,
- glyfTable.byteLength
- );
- const offsetSize = isGlyphLocationsLong ? 4 : 2;
- let prev = isGlyphLocationsLong ? loca.getUint32(0) : 2 * loca.getUint16(0);
- let pos = 0;
- for (let i = 0; i < numGlyphs; i++) {
- pos += offsetSize;
- const next = isGlyphLocationsLong
- ? loca.getUint32(pos)
- : 2 * loca.getUint16(pos);
- if (next === prev) {
- this.glyphs.push(new Glyph({}));
- continue;
- }
- const glyph = Glyph.parse(prev, glyf);
- this.glyphs.push(glyph);
- prev = next;
- }
- }
- getSize() {
- return this.glyphs.reduce((a, g) => {
- const size = g.getSize();
- // Round to next multiple of 4 if needed.
- return a + ((size + 3) & ~3);
- }, 0);
- }
- write() {
- const totalSize = this.getSize();
- const glyfTable = new DataView(new ArrayBuffer(totalSize));
- const isLocationLong = totalSize > /* 0xffff * 2 */ 0x1fffe;
- const offsetSize = isLocationLong ? 4 : 2;
- const locaTable = new DataView(
- new ArrayBuffer((this.glyphs.length + 1) * offsetSize)
- );
- if (isLocationLong) {
- locaTable.setUint32(0, 0);
- } else {
- locaTable.setUint16(0, 0);
- }
- let pos = 0;
- let locaIndex = 0;
- for (const glyph of this.glyphs) {
- pos += glyph.write(pos, glyfTable);
- // Round to next multiple of 4 if needed.
- pos = (pos + 3) & ~3;
- locaIndex += offsetSize;
- if (isLocationLong) {
- locaTable.setUint32(locaIndex, pos);
- } else {
- locaTable.setUint16(locaIndex, pos >> 1);
- }
- }
- return {
- isLocationLong,
- loca: new Uint8Array(locaTable.buffer),
- glyf: new Uint8Array(glyfTable.buffer),
- };
- }
- scale(factors) {
- for (let i = 0, ii = this.glyphs.length; i < ii; i++) {
- this.glyphs[i].scale(factors[i]);
- }
- }
- }
- class Glyph {
- constructor({ header = null, simple = null, composites = null }) {
- this.header = header;
- this.simple = simple;
- this.composites = composites;
- }
- static parse(pos, glyf) {
- const [read, header] = GlyphHeader.parse(pos, glyf);
- pos += read;
- if (header.numberOfContours < 0) {
- // Composite glyph.
- const composites = [];
- while (true) {
- const [n, composite] = CompositeGlyph.parse(pos, glyf);
- pos += n;
- composites.push(composite);
- if (!(composite.flags & MORE_COMPONENTS)) {
- break;
- }
- }
- return new Glyph({ header, composites });
- }
- const simple = SimpleGlyph.parse(pos, glyf, header.numberOfContours);
- return new Glyph({ header, simple });
- }
- getSize() {
- if (!this.header) {
- return 0;
- }
- const size = this.simple
- ? this.simple.getSize()
- : this.composites.reduce((a, c) => a + c.getSize(), 0);
- return this.header.getSize() + size;
- }
- write(pos, buf) {
- if (!this.header) {
- return 0;
- }
- const spos = pos;
- pos += this.header.write(pos, buf);
- if (this.simple) {
- pos += this.simple.write(pos, buf);
- } else {
- for (const composite of this.composites) {
- pos += composite.write(pos, buf);
- }
- }
- return pos - spos;
- }
- scale(factor) {
- if (!this.header) {
- return;
- }
- const xMiddle = (this.header.xMin + this.header.xMax) / 2;
- this.header.scale(xMiddle, factor);
- if (this.simple) {
- this.simple.scale(xMiddle, factor);
- } else {
- for (const composite of this.composites) {
- composite.scale(xMiddle, factor);
- }
- }
- }
- }
- class GlyphHeader {
- constructor({ numberOfContours, xMin, yMin, xMax, yMax }) {
- this.numberOfContours = numberOfContours;
- this.xMin = xMin;
- this.yMin = yMin;
- this.xMax = xMax;
- this.yMax = yMax;
- }
- static parse(pos, glyf) {
- return [
- 10,
- new GlyphHeader({
- numberOfContours: glyf.getInt16(pos),
- xMin: glyf.getInt16(pos + 2),
- yMin: glyf.getInt16(pos + 4),
- xMax: glyf.getInt16(pos + 6),
- yMax: glyf.getInt16(pos + 8),
- }),
- ];
- }
- getSize() {
- return 10;
- }
- write(pos, buf) {
- buf.setInt16(pos, this.numberOfContours);
- buf.setInt16(pos + 2, this.xMin);
- buf.setInt16(pos + 4, this.yMin);
- buf.setInt16(pos + 6, this.xMax);
- buf.setInt16(pos + 8, this.yMax);
- return 10;
- }
- scale(x, factor) {
- this.xMin = Math.round(x + (this.xMin - x) * factor);
- this.xMax = Math.round(x + (this.xMax - x) * factor);
- }
- }
- class Contour {
- constructor({ flags, xCoordinates, yCoordinates }) {
- this.xCoordinates = xCoordinates;
- this.yCoordinates = yCoordinates;
- this.flags = flags;
- }
- }
- class SimpleGlyph {
- constructor({ contours, instructions }) {
- this.contours = contours;
- this.instructions = instructions;
- }
- static parse(pos, glyf, numberOfContours) {
- const endPtsOfContours = [];
- for (let i = 0; i < numberOfContours; i++) {
- const endPt = glyf.getUint16(pos);
- pos += 2;
- endPtsOfContours.push(endPt);
- }
- const numberOfPt = endPtsOfContours[numberOfContours - 1] + 1;
- const instructionLength = glyf.getUint16(pos);
- pos += 2;
- const instructions = new Uint8Array(glyf).slice(
- pos,
- pos + instructionLength
- );
- pos += instructionLength;
- const flags = [];
- for (let i = 0; i < numberOfPt; pos++, i++) {
- let flag = glyf.getUint8(pos);
- flags.push(flag);
- if (flag & REPEAT_FLAG) {
- const count = glyf.getUint8(++pos);
- flag ^= REPEAT_FLAG;
- for (let m = 0; m < count; m++) {
- flags.push(flag);
- }
- i += count;
- }
- }
- const allXCoordinates = [];
- let xCoordinates = [];
- let yCoordinates = [];
- let pointFlags = [];
- const contours = [];
- let endPtsOfContoursIndex = 0;
- let lastCoordinate = 0;
- // Get x coordinates.
- for (let i = 0; i < numberOfPt; i++) {
- const flag = flags[i];
- if (flag & X_SHORT_VECTOR) {
- // 8-bits unsigned value.
- const x = glyf.getUint8(pos++);
- lastCoordinate += flag & X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR ? x : -x;
- xCoordinates.push(lastCoordinate);
- } else if (flag & X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR) {
- // IS_SAME.
- xCoordinates.push(lastCoordinate);
- } else {
- lastCoordinate += glyf.getInt16(pos);
- pos += 2;
- xCoordinates.push(lastCoordinate);
- }
- if (endPtsOfContours[endPtsOfContoursIndex] === i) {
- // Next entry is the first one of a new contour.
- endPtsOfContoursIndex++;
- allXCoordinates.push(xCoordinates);
- xCoordinates = [];
- }
- }
- lastCoordinate = 0;
- endPtsOfContoursIndex = 0;
- for (let i = 0; i < numberOfPt; i++) {
- const flag = flags[i];
- if (flag & Y_SHORT_VECTOR) {
- // 8-bits unsigned value.
- const y = glyf.getUint8(pos++);
- lastCoordinate += flag & Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR ? y : -y;
- yCoordinates.push(lastCoordinate);
- } else if (flag & Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR) {
- // IS_SAME.
- yCoordinates.push(lastCoordinate);
- } else {
- lastCoordinate += glyf.getInt16(pos);
- pos += 2;
- yCoordinates.push(lastCoordinate);
- }
- pointFlags.push((flag & ON_CURVE_POINT) | (flag & OVERLAP_SIMPLE));
- if (endPtsOfContours[endPtsOfContoursIndex] === i) {
- // Next entry is the first one of a new contour.
- xCoordinates = allXCoordinates[endPtsOfContoursIndex];
- endPtsOfContoursIndex++;
- contours.push(
- new Contour({
- flags: pointFlags,
- xCoordinates,
- yCoordinates,
- })
- );
- yCoordinates = [];
- pointFlags = [];
- }
- }
- return new SimpleGlyph({
- contours,
- instructions,
- });
- }
- getSize() {
- let size = this.contours.length * 2 + 2 + this.instructions.length;
- let lastX = 0;
- let lastY = 0;
- for (const contour of this.contours) {
- size += contour.flags.length;
- for (let i = 0, ii = contour.xCoordinates.length; i < ii; i++) {
- const x = contour.xCoordinates[i];
- const y = contour.yCoordinates[i];
- let abs = Math.abs(x - lastX);
- if (abs > 255) {
- size += 2;
- } else if (abs > 0) {
- size += 1;
- }
- lastX = x;
- abs = Math.abs(y - lastY);
- if (abs > 255) {
- size += 2;
- } else if (abs > 0) {
- size += 1;
- }
- lastY = y;
- }
- }
- return size;
- }
- write(pos, buf) {
- const spos = pos;
- const xCoordinates = [];
- const yCoordinates = [];
- const flags = [];
- let lastX = 0;
- let lastY = 0;
- for (const contour of this.contours) {
- for (let i = 0, ii = contour.xCoordinates.length; i < ii; i++) {
- let flag = contour.flags[i];
- const x = contour.xCoordinates[i];
- let delta = x - lastX;
- if (delta === 0) {
- flag |= X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR;
- xCoordinates.push(0);
- } else {
- const abs = Math.abs(delta);
- if (abs <= 255) {
- flag |=
- delta >= 0
- ? X_SHORT_VECTOR | X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR
- : X_SHORT_VECTOR;
- xCoordinates.push(abs);
- } else {
- xCoordinates.push(delta);
- }
- }
- lastX = x;
- const y = contour.yCoordinates[i];
- delta = y - lastY;
- if (delta === 0) {
- flag |= Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR;
- yCoordinates.push(0);
- } else {
- const abs = Math.abs(delta);
- if (abs <= 255) {
- flag |=
- delta >= 0
- ? Y_SHORT_VECTOR | Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR
- : Y_SHORT_VECTOR;
- yCoordinates.push(abs);
- } else {
- yCoordinates.push(delta);
- }
- }
- lastY = y;
- flags.push(flag);
- }
- // Write endPtsOfContours entry.
- buf.setUint16(pos, xCoordinates.length - 1);
- pos += 2;
- }
- // Write instructionLength.
- buf.setUint16(pos, this.instructions.length);
- pos += 2;
- if (this.instructions.length) {
- // Write instructions.
- new Uint8Array(buf.buffer, 0, buf.buffer.byteLength).set(
- this.instructions,
- pos
- );
- pos += this.instructions.length;
- }
- // Write flags.
- for (const flag of flags) {
- buf.setUint8(pos++, flag);
- }
- // Write xCoordinates.
- for (let i = 0, ii = xCoordinates.length; i < ii; i++) {
- const x = xCoordinates[i];
- const flag = flags[i];
- if (flag & X_SHORT_VECTOR) {
- buf.setUint8(pos++, x);
- } else if (!(flag & X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR)) {
- buf.setInt16(pos, x);
- pos += 2;
- }
- }
- // Write yCoordinates.
- for (let i = 0, ii = yCoordinates.length; i < ii; i++) {
- const y = yCoordinates[i];
- const flag = flags[i];
- if (flag & Y_SHORT_VECTOR) {
- buf.setUint8(pos++, y);
- } else if (!(flag & Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR)) {
- buf.setInt16(pos, y);
- pos += 2;
- }
- }
- return pos - spos;
- }
- scale(x, factor) {
- for (const contour of this.contours) {
- if (contour.xCoordinates.length === 0) {
- continue;
- }
- for (let i = 0, ii = contour.xCoordinates.length; i < ii; i++) {
- contour.xCoordinates[i] = Math.round(
- x + (contour.xCoordinates[i] - x) * factor
- );
- }
- }
- }
- }
- class CompositeGlyph {
- constructor({
- flags,
- glyphIndex,
- argument1,
- argument2,
- transf,
- instructions,
- }) {
- this.flags = flags;
- this.glyphIndex = glyphIndex;
- this.argument1 = argument1;
- this.argument2 = argument2;
- this.transf = transf;
- this.instructions = instructions;
- }
- static parse(pos, glyf) {
- const spos = pos;
- const transf = [];
- let flags = glyf.getUint16(pos);
- const glyphIndex = glyf.getUint16(pos + 2);
- pos += 4;
- let argument1, argument2;
- if (flags & ARG_1_AND_2_ARE_WORDS) {
- if (flags & ARGS_ARE_XY_VALUES) {
- argument1 = glyf.getInt16(pos);
- argument2 = glyf.getInt16(pos + 2);
- } else {
- argument1 = glyf.getUint16(pos);
- argument2 = glyf.getUint16(pos + 2);
- }
- pos += 4;
- flags ^= ARG_1_AND_2_ARE_WORDS;
- } else {
- if (flags & ARGS_ARE_XY_VALUES) {
- argument1 = glyf.getInt8(pos);
- argument2 = glyf.getInt8(pos + 1);
- } else {
- argument1 = glyf.getUint8(pos);
- argument2 = glyf.getUint8(pos + 1);
- }
- pos += 2;
- }
- if (flags & WE_HAVE_A_SCALE) {
- // Single F2.14.
- transf.push(glyf.getUint16(pos));
- pos += 2;
- } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) {
- // Two F2.14.
- transf.push(glyf.getUint16(pos), glyf.getUint16(pos + 2));
- pos += 4;
- } else if (flags & WE_HAVE_A_TWO_BY_TWO) {
- // Four F2.14.
- transf.push(
- glyf.getUint16(pos),
- glyf.getUint16(pos + 2),
- glyf.getUint16(pos + 4),
- glyf.getUint16(pos + 6)
- );
- pos += 8;
- }
- let instructions = null;
- if (flags & WE_HAVE_INSTRUCTIONS) {
- const instructionLength = glyf.getUint16(pos);
- pos += 2;
- instructions = new Uint8Array(glyf).slice(pos, pos + instructionLength);
- pos += instructionLength;
- }
- return [
- pos - spos,
- new CompositeGlyph({
- flags,
- glyphIndex,
- argument1,
- argument2,
- transf,
- instructions,
- }),
- ];
- }
- getSize() {
- let size = 2 + 2 + this.transf.length * 2;
- if (this.flags & WE_HAVE_INSTRUCTIONS) {
- size += 2 + this.instructions.length;
- }
- size += 2;
- if (this.flags & 2) {
- // Arguments are signed.
- if (
- !(
- this.argument1 >= -128 &&
- this.argument1 <= 127 &&
- this.argument2 >= -128 &&
- this.argument2 <= 127
- )
- ) {
- size += 2;
- }
- } else {
- if (
- !(
- this.argument1 >= 0 &&
- this.argument1 <= 255 &&
- this.argument2 >= 0 &&
- this.argument2 <= 255
- )
- ) {
- size += 2;
- }
- }
- return size;
- }
- write(pos, buf) {
- const spos = pos;
- if (this.flags & ARGS_ARE_XY_VALUES) {
- // Arguments are signed.
- if (
- !(
- this.argument1 >= -128 &&
- this.argument1 <= 127 &&
- this.argument2 >= -128 &&
- this.argument2 <= 127
- )
- ) {
- this.flags |= ARG_1_AND_2_ARE_WORDS;
- }
- } else {
- if (
- !(
- this.argument1 >= 0 &&
- this.argument1 <= 255 &&
- this.argument2 >= 0 &&
- this.argument2 <= 255
- )
- ) {
- this.flags |= ARG_1_AND_2_ARE_WORDS;
- }
- }
- buf.setUint16(pos, this.flags);
- buf.setUint16(pos + 2, this.glyphIndex);
- pos += 4;
- if (this.flags & ARG_1_AND_2_ARE_WORDS) {
- if (this.flags & ARGS_ARE_XY_VALUES) {
- buf.setInt16(pos, this.argument1);
- buf.setInt16(pos + 2, this.argument2);
- } else {
- buf.setUint16(pos, this.argument1);
- buf.setUint16(pos + 2, this.argument2);
- }
- pos += 4;
- } else {
- buf.setUint8(pos, this.argument1);
- buf.setUint8(pos + 1, this.argument2);
- pos += 2;
- }
- if (this.flags & WE_HAVE_INSTRUCTIONS) {
- buf.setUint16(pos, this.instructions.length);
- pos += 2;
- // Write instructions.
- if (this.instructions.length) {
- new Uint8Array(buf.buffer, 0, buf.buffer.byteLength).set(
- this.instructions,
- pos
- );
- pos += this.instructions.length;
- }
- }
- return pos - spos;
- }
- scale(x, factor) {}
- }
- export { GlyfTable };
|