@@ -83,6 +83,7 @@ function bytesToString(bytes) {
8383const typeOffsets = {
8484 byte : 1 ,
8585 uShort : 2 ,
86+ f2dot14 : 2 ,
8687 short : 2 ,
8788 uInt24 : 3 ,
8889 uLong : 4 ,
@@ -93,7 +94,18 @@ const typeOffsets = {
9394
9495const masks = {
9596 LONG_WORDS : 0x8000 ,
96- WORD_DELTA_COUNT_MASK : 0x7FFF
97+ WORD_DELTA_COUNT_MASK : 0x7FFF ,
98+ SHARED_POINT_NUMBERS : 0x8000 ,
99+ COUNT_MASK : 0x0FFF ,
100+ EMBEDDED_PEAK_TUPLE : 0x8000 ,
101+ INTERMEDIATE_REGION : 0x4000 ,
102+ PRIVATE_POINT_NUMBERS : 0x2000 ,
103+ TUPLE_INDEX_MASK : 0x0FFF ,
104+ POINTS_ARE_WORDS : 0x80 ,
105+ POINT_RUN_COUNT_MASK : 0x7F ,
106+ DELTAS_ARE_ZERO : 0x80 ,
107+ DELTAS_ARE_WORDS : 0x40 ,
108+ DELTA_RUN_COUNT_MASK : 0x3F ,
97109} ;
98110
99111// A stateful parser that changes the offset whenever a value is retrieved.
@@ -346,6 +358,18 @@ Parser.prototype.parseRecordList32 = function(count, recordDescription) {
346358 return records ;
347359} ;
348360
361+ Parser . prototype . parseTupleRecords = function ( tupleCount , axisCount ) {
362+ let tuples = [ ] ;
363+ for ( let i = 0 ; i < tupleCount ; i ++ ) {
364+ let tuple = [ ] ;
365+ for ( let axisIndex = 0 ; axisIndex < axisCount ; axisIndex ++ ) {
366+ tuple . push ( this . parseF2Dot14 ( ) ) ;
367+ }
368+ tuples . push ( tuple ) ;
369+ }
370+ return tuples ;
371+ } ;
372+
349373// Parse a data structure into an object
350374// Example of description: { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort }
351375Parser . prototype . parseStruct = function ( description ) {
@@ -712,6 +736,253 @@ Parser.prototype.parseDeltaSets = function(itemCount, wordDeltaCount) {
712736 return deltas ;
713737} ;
714738
739+ Parser . prototype . parseTupleVariationStoreList = function ( axisCount , flavor , glyphs ) {
740+ const glyphCount = this . parseUShort ( ) ;
741+ const flags = this . parseUShort ( ) ;
742+ const offsetSizeIs32Bit = flags & 0x01 ;
743+
744+ const glyphVariationDataArrayOffset = this . parseOffset32 ( ) ;
745+ const parseOffset = ( offsetSizeIs32Bit ? this . parseULong : this . parseUShort ) . bind ( this ) ;
746+
747+ const glyphVariations = { } ;
748+
749+ let currentOffset = parseOffset ( ) ;
750+ if ( ! offsetSizeIs32Bit ) currentOffset *= 2 ;
751+ let nextOffset ;
752+
753+ for ( let i = 0 ; i < glyphCount ; i ++ ) {
754+ nextOffset = parseOffset ( ) ;
755+ if ( ! offsetSizeIs32Bit ) nextOffset *= 2 ;
756+
757+ const length = nextOffset - currentOffset ;
758+
759+ glyphVariations [ i ] = length
760+ ? this . parseTupleVariationStore (
761+ glyphVariationDataArrayOffset + currentOffset ,
762+ axisCount ,
763+ flavor ,
764+ glyphs ,
765+ i
766+ )
767+ : undefined ;
768+
769+ currentOffset = nextOffset ;
770+ }
771+
772+ return glyphVariations ;
773+ } ;
774+
775+ Parser . prototype . parseTupleVariationStore = function ( tableOffset , axisCount , flavor , glyphs , glyphIndex ) {
776+ const relativeOffset = this . relativeOffset ;
777+
778+ this . relativeOffset = tableOffset ;
779+ if ( flavor === 'cvar' ) {
780+ this . relativeOffset += 4 ; // we already parsed the version fields in cvar.js directly
781+ }
782+
783+ // header
784+ const tupleVariationCount = this . parseUShort ( ) ;
785+ const hasSharedPoints = ! ! ( tupleVariationCount & masks . SHARED_POINT_NUMBERS ) ;
786+ // const reserved = tupleVariationCount & 0x7000;
787+ const count = tupleVariationCount & masks . COUNT_MASK ;
788+ let dataOffset = this . parseOffset16 ( ) ;
789+ const headers = [ ] ;
790+ let sharedPoints = [ ] ;
791+
792+ for ( let h = 0 ; h < count ; h ++ ) {
793+ const headerData = this . parseTupleVariationHeader ( axisCount , flavor ) ;
794+ headers . push ( headerData ) ;
795+ }
796+
797+ if ( this . relativeOffset !== tableOffset + dataOffset ) {
798+ console . warn ( `Unexpected offset after parsing tuple variation headers! Expected ${ tableOffset + dataOffset } , actually ${ this . relativeOffset } ` ) ;
799+ this . relativeOffset = tableOffset + dataOffset ;
800+ }
801+
802+ if ( flavor === 'gvar' && hasSharedPoints ) {
803+ sharedPoints = this . parsePackedPointNumbers ( ) ;
804+ }
805+
806+ let serializedDataOffset = this . relativeOffset ;
807+
808+ for ( let h = 0 ; h < count ; h ++ ) {
809+ const header = headers [ h ] ;
810+ header . privatePoints = [ ] ;
811+ this . relativeOffset = serializedDataOffset ;
812+
813+ if ( flavor === 'cvar' && ! header . peakTuple ) {
814+ console . warn ( 'An embedded peak tuple is required in TupleVariationHeaders for the cvar table.' ) ;
815+ }
816+
817+ if ( header . flags . privatePointNumbers ) {
818+ header . privatePoints = this . parsePackedPointNumbers ( ) ;
819+ }
820+
821+ const deltasOffset = this . offset ;
822+ const deltasRelativeOffset = this . relativeOffset ;
823+
824+ const defineDeltas = ( propertyName ) => {
825+ let _deltas = undefined ;
826+ let _deltasY = undefined ;
827+
828+ const parseDeltas = ( ) => {
829+ let pointsCount = header . privatePoints . length || sharedPoints . length ;
830+ if ( ! pointsCount ) {
831+ if ( flavor === 'gvar' ) {
832+ const glyph = glyphs . get ( glyphIndex ) ;
833+ // make sure the path is available
834+ glyph . path ;
835+ pointsCount = glyph . points . length ;
836+ // add 4 phantom points, see https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructing_glyphs#phantoms
837+ // @TODO : actually generate these points from glyph.getBoundingBox() and glyph.getMetrics(),
838+ // as they may be influenced by variation as well
839+ pointsCount += 4 ;
840+ } else if ( flavor === 'cvar' ) {
841+ pointsCount = glyphs . length ; // glyphs here is actually font.tables.cvt
842+ }
843+ }
844+
845+ this . offset = deltasOffset ;
846+ this . relativeOffset = deltasRelativeOffset ;
847+ _deltas = this . parsePackedDeltas ( pointsCount ) ;
848+
849+ if ( flavor === 'gvar' ) {
850+ _deltasY = this . parsePackedDeltas ( pointsCount ) ;
851+ }
852+ } ;
853+
854+ return {
855+ configurable : true ,
856+
857+ get : function ( ) {
858+ if ( _deltas === undefined ) parseDeltas ( ) ;
859+ return propertyName === 'deltasY' ? _deltasY : _deltas ;
860+ } ,
861+
862+ set : function ( deltas ) {
863+ if ( _deltas === undefined ) parseDeltas ( ) ;
864+ if ( propertyName === 'deltasY' ) {
865+ _deltasY = deltas ;
866+ } else {
867+ _deltas = deltas ;
868+ }
869+ }
870+ } ;
871+ } ;
872+
873+ Object . defineProperty ( header , 'deltas' , defineDeltas . call ( this , 'deltas' ) ) ;
874+ if ( flavor === 'gvar' ) {
875+ Object . defineProperty ( header , 'deltasY' , defineDeltas . call ( this , 'deltasY' ) ) ;
876+ }
877+
878+ serializedDataOffset += header . variationDataSize ;
879+ delete header . variationDataSize ; // we don't need to expose this
880+ }
881+
882+ this . relativeOffset = relativeOffset ;
883+ const result = {
884+ headers,
885+ } ;
886+
887+ if ( flavor === 'gvar' ) {
888+ result . sharedPoints = sharedPoints ;
889+ }
890+
891+ return result ;
892+ } ;
893+
894+ Parser . prototype . parseTupleVariationHeader = function ( axisCount , flavor ) {
895+ const variationDataSize = this . parseUShort ( ) ;
896+ const tupleIndex = this . parseUShort ( ) ;
897+
898+ const embeddedPeakTuple = ! ! ( tupleIndex & masks . EMBEDDED_PEAK_TUPLE ) ;
899+ const intermediateRegion = ! ! ( tupleIndex & masks . INTERMEDIATE_REGION ) ;
900+ const privatePointNumbers = ! ! ( tupleIndex & masks . PRIVATE_POINT_NUMBERS ) ;
901+ // const reserved = tupleIndex & 0x1000;
902+ const sharedTupleRecordsIndex = embeddedPeakTuple ? undefined : tupleIndex & masks . TUPLE_INDEX_MASK ;
903+
904+ const peakTuple = embeddedPeakTuple ? this . parseTupleRecords ( 1 , axisCount ) [ 0 ] : undefined ;
905+ const intermediateStartTuple = intermediateRegion ? this . parseTupleRecords ( 1 , axisCount ) [ 0 ] : undefined ;
906+ const intermediateEndTuple = intermediateRegion ? this . parseTupleRecords ( 1 , axisCount ) [ 0 ] : undefined ;
907+
908+ const result = {
909+ variationDataSize,
910+ peakTuple,
911+ intermediateStartTuple,
912+ intermediateEndTuple,
913+ flags : {
914+ embeddedPeakTuple,
915+ intermediateRegion,
916+ privatePointNumbers,
917+ }
918+ } ;
919+
920+ if ( flavor === 'gvar' ) {
921+ result . sharedTupleRecordsIndex = sharedTupleRecordsIndex ;
922+ }
923+
924+ return result ;
925+ } ;
926+
927+ Parser . prototype . parsePackedPointNumbers = function ( ) {
928+ const countByte1 = this . parseByte ( ) ;
929+ const points = [ ] ;
930+ let totalPointCount = countByte1 ;
931+
932+ if ( countByte1 >= 128 ) {
933+ // High bit is set, need to read a second byte and combine.
934+ const countByte2 = this . parseByte ( ) ;
935+
936+ // Combine as big-endian uint16, with high bit of the first byte cleared.
937+ // This is done by masking the first byte with 0x7F (to clear the high bit)
938+ // and then shifting it left by 8 bits before adding the second byte.
939+ totalPointCount = ( ( countByte1 & masks . POINT_RUN_COUNT_MASK ) << 8 ) | countByte2 ;
940+ }
941+
942+ let lastPoint = 0 ;
943+ while ( points . length < totalPointCount ) {
944+ const controlByte = this . parseByte ( ) ;
945+ const numbersAre16Bit = ! ! ( controlByte & masks . POINTS_ARE_WORDS ) ; // Check if high bit is set
946+ let runCount = ( controlByte & masks . POINT_RUN_COUNT_MASK ) + 1 ; // Number of points in this run
947+ for ( let i = 0 ; i < runCount && points . length < totalPointCount ; i ++ ) {
948+ let pointDelta ;
949+ if ( numbersAre16Bit ) {
950+ pointDelta = this . parseUShort ( ) ; // Parse delta as uint16
951+ } else {
952+ pointDelta = this . parseByte ( ) ; // Parse delta as uint8
953+ }
954+ // For the first point of the first run, use the value directly. Otherwise, accumulate.
955+ lastPoint = lastPoint + pointDelta ;
956+ points . push ( lastPoint ) ;
957+ }
958+ }
959+
960+ return points ;
961+ } ;
962+
963+ Parser . prototype . parsePackedDeltas = function ( expectedCount ) {
964+ const deltas = [ ] ;
965+
966+ while ( deltas . length < expectedCount ) {
967+ const controlByte = this . parseByte ( ) ;
968+ const zeroData = ! ! ( controlByte & masks . DELTAS_ARE_ZERO ) ;
969+ const deltaWords = ! ! ( controlByte & masks . DELTAS_ARE_WORDS ) ;
970+ const runCount = ( controlByte & masks . DELTA_RUN_COUNT_MASK ) + 1 ;
971+
972+ for ( let i = 0 ; i < runCount && deltas . length < expectedCount ; i ++ ) {
973+ if ( zeroData ) {
974+ deltas . push ( 0 ) ;
975+ } else if ( deltaWords ) {
976+ deltas . push ( this . parseShort ( ) ) ;
977+ } else {
978+ deltas . push ( this . parseChar ( ) ) ;
979+ }
980+ }
981+ }
982+
983+ return deltas ;
984+ } ;
985+
715986export default {
716987 getByte,
717988 getCard8 : getByte ,
0 commit comments