shithub: fnt

Download patch

ref: 2e10d81b777a344cc4d9fecaf50638ee29312428
parent: f5c1a9365b39548d8346227e72def71468bbf0ba
author: Sigrid Solveig Haflínudóttir <sigrid@ftrv.se>
date: Sun Jul 28 15:21:16 EDT 2024

poor person's subpixel rendering with a random FIR

--- a/otf.c.in
+++ b/otf.c.in
@@ -659,7 +659,7 @@
 }
 
 static Glyf *
-bitglyf(Otf *o, Glyf *g, double ppemX, double ppemY)
+bitglyf(Otf *o, Glyf *g, RasterOpts *opts)
 {
 	BitmapGlyph b, best, want;
 	IndexSubtableRecord *isr;
@@ -667,8 +667,8 @@
 	IndexSubtable *is;
 	BitmapSize *bs;
 
-	want.ppemX = ppemX;
-	want.ppemY = ppemY;
+	want.ppemX = opts->ppemX;
+	want.ppemY = opts->ppemY;
 	found = 0;
 	r = -1;
 
@@ -770,7 +770,7 @@
 }
 
 Glyf *
-otfglyf(Otf *o, int index, double ppemX, double ppemY)
+otfglyf(Otf *o, int index, RasterOpts *opts)
 {
 	int off, len, i;
 	Glyf *g;
@@ -839,7 +839,7 @@
 			goto err;
 		}
 		g->index = index;
-		g = bitglyf(o, g, ppemX, ppemY);
+		g = bitglyf(o, g, opts);
 	}
 
 	return g;
--- a/otf.h.in
+++ b/otf.h.in
@@ -115,21 +115,50 @@
 void otfprint(Otf *o, Otfile *out, int indent);
 void otfclose(Otf *o);
 
-/* FIXME these might go eventually */
+/*
+ * FIXME this API *will* change, maybe not even once.
+ * atm it's just to get things going, for a start.
+ */
 
 typedef struct Glyf Glyf;
 typedef struct GlyfImage GlyfImage;
+typedef struct RasterOpts RasterOpts;
+typedef struct SubPx SubPx;
 
+enum {
+	GLYF_FMT_GREY_8,
+	GLYF_FMT_RGB_24,
+};
+
+enum {
+	LCD_RGB,
+};
+
 struct GlyfImage {
 	int w;
 	int h;
 	int baseline;
+	int fmt;
 	u8int b[];
 };
 
+struct SubPx {
+	struct {
+		u8int *c;
+		int nc;
+	}fir;
+	int lcd;
+};
+
+struct RasterOpts {
+	double ppemX;
+	double ppemY;
+	SubPx *subpx;
+};
+
 int otfglyfnum(Otf *o);
 int otfrune2glyph(Otf *o, Rune r);
 Rune otfglyph2rune(Otf *o, int g);
 
-Glyf *otfglyf(Otf *o, int index, double ppemX, double ppemY);
-GlyfImage *otfdrawglyf(Otf *o, Glyf *g, double ppemX, double ppemY);
+Glyf *otfglyf(Otf *o, int index, RasterOpts *opts);
+GlyfImage *otfdrawglyf(Otf *o, Glyf *g, RasterOpts *opts);
--- a/otf.rkt
+++ b/otf.rkt
@@ -455,6 +455,103 @@
 
 (mkcmplx FeatureList {uint16 featureCount} {FeatureRecord featureRecords [featureCount]})
 
+(mkcmplx SequenceLookup {uint16 sequenceIndex} {uint16 lookupListIndex})
+
+(mkcmplx ChainedSequenceRule
+         {uint16 backtrackGlyphCount}
+         {uint16 backtrackSequence [backtrackGlyphCount]}
+         {uint16 inputGlyphCount}
+         {uint16 inputSequence [- inputGlyphCount 1]}
+         {uint16 lookaheadGlyphCount}
+         {uint16 lookaheadSequence [lookaheadGlyphCount]}
+         {uint16 seqLookupCount}
+         {SequenceLookup seqLookupRecords [seqLookupCount]})
+
+(mkcmplx ChainedSequenceRuleSet
+         {uint16 chainedSeqRuleCount}
+         {Offset16 chainedSeqRuleOffsets [chainedSeqRuleCount]}
+         {ChainedSequenceRule chainedSeqRule [chainedSeqRuleCount] (at chainedSeqRuleOffsets)})
+
+(mkcmplx
+ ChainedSequenceContextFormat1
+ {Offset16 coverageOffset}
+ {uint16 chainedSeqRuleSetCount}
+ {Offset16 chainedSeqRuleSetOffsets [chainedSeqRuleSetCount]}
+ {ChainedSequenceRuleSet chainedSeqRuleSet [chainedSeqRuleSetCount] (at chainedSeqRuleSetOffsets)})
+
+(mkcmplx ChainedClassSequenceRuleSet
+         {uint16 chainedClassSeqRuleCount}
+         {Offset16 chainedClassSeqRuleOffsets [chainedClassSeqRuleCount]}
+         {ChainedSequenceRule
+          chainedClassSeqRules
+          [chainedClassSeqRuleCount]
+          (at chainedClassSeqRuleOffsets)})
+
+(mkcmplx ChainedSequenceContextFormat2
+         {Offset16 coverageOffset}
+         {Offset16 backtrackClassDefOffset}
+         {Offset16 inputClassDefOffset}
+         {Offset16 lookaheadClassDefOffset}
+         {uint16 chainedClassSeqRuleSetCount}
+         {Offset16 chainedClassSeqRuleSetOffsets [chainedClassSeqRuleSetCount]}
+         {ChainedClassSequenceRuleSet
+          chainedClassSequenceRuleSets
+          [chainedClassSeqRuleSetCount]
+          (at chainedClassSeqRuleSetOffsets)})
+
+(mkcmplx ChainedSequenceContextFormat3
+         {uint16 backtrackGlyphCount}
+         {Offset16 backtrackCoverageOffsets [backtrackGlyphCount]}
+         {uint16 inputGlyphCount}
+         {Offset16 inputCoverageOffsets [inputGlyphCount]}
+         {uint16 lookaheadGlyphCount}
+         {Offset16 lookaheadCoverageOffsets [lookaheadGlyphCount]}
+         {uint16 seqLookupCount}
+         {SequenceLookup seqLookupRecords [seqLookupCount]})
+
+(mkcmplx ChainedSequenceContext
+         {uint16 format}
+         ; FIXME union
+         {ChainedSequenceContextFormat1 f1 (== format 1)}
+         {ChainedSequenceContextFormat2 f2 (== format 2)}
+         {ChainedSequenceContextFormat3 f3 (== format 3)})
+
+(mkcmplx PairValue {uint16 secondGlyph} {ValueRecord valueRecord1} {ValueRecord valueRecord2})
+
+(mkcmplx PairSet {uint16 pairValueCount} {PairValue pairValueRecords [pairValueCount]})
+
+(mkcmplx PairPosFormat1
+         {uint16 pairSetCount}
+         {Offset16 pairSetOffsets [pairSetCount]}
+         {PairSet pairSets [pairSetCount] (at pairSetOffsets)})
+
+(mkcmplx Class2 {ValueRecord valueRecord1} {ValueRecord valueRecord2})
+
+(mkcmplx Class1 {Class2 class2Records [o->class2Count]})
+
+(mkcmplx PairPosFormat2
+         {Offset16 classDef1Offset}
+         {Offset16 classDef2Offset}
+         {uint16 class1Count}
+         {uint16 class2Count ->o}
+         {Class1 class1Records [class1Count]})
+
+(mkcmplx PairPos
+         {uint16 format (== 1 2)}
+         {Offset16 coverageOffset}
+         {uint16 valueFormat1}
+         {uint16 valueFormat2}
+         {PairPosFormat1 f1 (== format 1)}
+         {PairPosFormat2 f2 (== format 2)})
+
+(mkcmplx EntryExit {Offset16 entryAnchorOffset} {Offset16 exitAnchorOffset})
+
+(mkcmplx CursivePos
+         {uint16 format (== 1) unused}
+         {Offset16 coverageOffset}
+         {uint16 entryExitCount}
+         {EntryExit entryExitRecords [entryExitCount]})
+
 (define lookupFlags
   #hash((0 . LOOKUP_FL_RIGHT_TO_LEFT)
         (1 . LOOKUP_FL_IGNORE_BASE_GLYPHS)
@@ -479,8 +576,29 @@
          {uint16 lookupFlag hex (bits lookupFlags)}
          {uint16 subTableCount}
          {Offset16 subtableOffsets [subTableCount]}
-         {uint16 markFilteringSet})
-; FIXME lookup subtable based on current table (GPOS vs GSUB) and lookupType
+         {uint16 markFilteringSet}
+         ; FIXME union
+         {SinglePos
+          singleAdjustment
+          [subTableCount]
+          (at subtableOffsets)
+          (== lookupType LOOKUP_TYPE_SINGLE_ADJUSTMENT)}
+         {PairPos
+          pairAdjustment
+          [subTableCount]
+          (at subtableOffsets)
+          (== lookupType LOOKUP_TYPE_PAIR_ADJUSTMENT)}
+         {CursivePos
+          cursiveAttachment
+          [subTableCount]
+          (at subtableOffsets)
+          (== lookupType LOOKUP_TYPE_CURSIVE_ATTACHMENT)}
+         ; FIXME add more
+         {ChainedSequenceContext
+          chainedContextsPositioning
+          [subTableCount]
+          (at subtableOffsets)
+          (== lookupType LOOKUP_TYPE_CHAINED_CONTEXTS_POSITIONING)})
 
 (mkcmplx LookupList
          {uint16 lookupCount}
--- a/plan9/otf.c
+++ b/plan9/otf.c
@@ -660,7 +660,7 @@
 }
 
 static Glyf *
-bitglyf(Otf *o, Glyf *g, double ppemX, double ppemY)
+bitglyf(Otf *o, Glyf *g, RasterOpts *opts)
 {
 	BitmapGlyph b, best, want;
 	IndexSubtableRecord *isr;
@@ -668,8 +668,8 @@
 	IndexSubtable *is;
 	BitmapSize *bs;
 
-	want.ppemX = ppemX;
-	want.ppemY = ppemY;
+	want.ppemX = opts->ppemX;
+	want.ppemY = opts->ppemY;
 	found = 0;
 	r = -1;
 
@@ -771,7 +771,7 @@
 }
 
 Glyf *
-otfglyf(Otf *o, int index, double ppemX, double ppemY)
+otfglyf(Otf *o, int index, RasterOpts *opts)
 {
 	int off, len, i;
 	Glyf *g;
@@ -840,7 +840,7 @@
 			goto err;
 		}
 		g->index = index;
-		g = bitglyf(o, g, ppemX, ppemY);
+		g = bitglyf(o, g, opts);
 	}
 
 	return g;
@@ -3479,6 +3479,700 @@
 }
 
 int
+read_SequenceLookup(Otf *o, SequenceLookup *v)
+{
+	u8int *b = nil; USED(b);
+	if((b = otfreadn(o, 4)) == nil)
+		goto err;
+	v->sequenceIndex = b[0]<<8 | b[1];
+	v->lookupListIndex = b[2]<<8 | b[3];
+	return 0;
+err:
+	werrstr("%s: %r", "SequenceLookup");
+	return -1;
+}
+
+void
+print_SequenceLookup(Otfile *f, int indent, Otf *o, SequenceLookup *v)
+{
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "sequenceIndex", v->sequenceIndex);
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "lookupListIndex", v->lookupListIndex);
+	USED(o);
+}
+
+int
+read_ChainedSequenceRule(Otf *o, ChainedSequenceRule *v)
+{
+	u8int *b = nil; USED(b);
+	if((b = otfreadn(o, 2)) == nil)
+		goto err;
+	v->backtrackGlyphCount = b[0]<<8 | b[1];
+	if((b = otfreadn(o, v->backtrackGlyphCount*2)) == nil)
+		goto err;
+	v->backtrackSequence = malloc(v->backtrackGlyphCount*sizeof(*v->backtrackSequence));
+	for(int i = 0; i < v->backtrackGlyphCount; i++)
+		v->backtrackSequence[i] = b[0+i*2]<<8 | b[1+i*2];
+	if((b = otfreadn(o, 2)) == nil)
+		goto err;
+	v->inputGlyphCount = b[0]<<8 | b[1];
+	if((b = otfreadn(o, (v->inputGlyphCount-1)*2)) == nil)
+		goto err;
+	v->inputSequence = malloc((v->inputGlyphCount-1)*sizeof(*v->inputSequence));
+	for(int i = 0; i < (v->inputGlyphCount-1); i++)
+		v->inputSequence[i] = b[0+i*2]<<8 | b[1+i*2];
+	if((b = otfreadn(o, 2)) == nil)
+		goto err;
+	v->lookaheadGlyphCount = b[0]<<8 | b[1];
+	if((b = otfreadn(o, v->lookaheadGlyphCount*2)) == nil)
+		goto err;
+	v->lookaheadSequence = malloc(v->lookaheadGlyphCount*sizeof(*v->lookaheadSequence));
+	for(int i = 0; i < v->lookaheadGlyphCount; i++)
+		v->lookaheadSequence[i] = b[0+i*2]<<8 | b[1+i*2];
+	if((b = otfreadn(o, 2)) == nil)
+		goto err;
+	v->seqLookupCount = b[0]<<8 | b[1];
+	if(otfarray(o, &v->seqLookupRecords, read_SequenceLookup, sizeof(SequenceLookup), v->seqLookupCount) < 0){
+		werrstr("%s: %r", "seqLookupRecords");
+		goto err;
+	}
+	return 0;
+err:
+	werrstr("%s: %r", "ChainedSequenceRule");
+	return -1;
+}
+
+void
+print_ChainedSequenceRule(Otfile *f, int indent, Otf *o, ChainedSequenceRule *v)
+{
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "backtrackGlyphCount", v->backtrackGlyphCount);
+	for(int i = 0; i < v->backtrackGlyphCount; i++)
+		f->print(f->aux, "%*s%s[%d]: %ud\n", indent, "", "backtrackSequence", i, v->backtrackSequence[i]);
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "inputGlyphCount", v->inputGlyphCount);
+	for(int i = 0; i < (v->inputGlyphCount-1); i++)
+		f->print(f->aux, "%*s%s[%d]: %ud\n", indent, "", "inputSequence", i, v->inputSequence[i]);
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "lookaheadGlyphCount", v->lookaheadGlyphCount);
+	for(int i = 0; i < v->lookaheadGlyphCount; i++)
+		f->print(f->aux, "%*s%s[%d]: %ud\n", indent, "", "lookaheadSequence", i, v->lookaheadSequence[i]);
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "seqLookupCount", v->seqLookupCount);
+	for(int i = 0; i < v->seqLookupCount; i++){
+		f->print(f->aux, "%*s%s[%d]:\n", indent, "", "seqLookupRecords", i);
+		print_SequenceLookup(f, indent+indentΔ, o, &v->seqLookupRecords[i]);
+	}
+	USED(o);
+}
+
+int
+read_ChainedSequenceRuleSet(Otf *o, ChainedSequenceRuleSet *v)
+{
+	u8int *b = nil; USED(b);
+	if((b = otfreadn(o, 2)) == nil)
+		goto err;
+	v->chainedSeqRuleCount = b[0]<<8 | b[1];
+	if((b = otfreadn(o, v->chainedSeqRuleCount*2)) == nil)
+		goto err;
+	v->chainedSeqRuleOffsets = malloc(v->chainedSeqRuleCount*sizeof(*v->chainedSeqRuleOffsets));
+	for(int i = 0; i < v->chainedSeqRuleCount; i++)
+		v->chainedSeqRuleOffsets[i] = b[0+i*2]<<8 | b[1+i*2];
+	if(v->chainedSeqRuleCount > 0){
+		v->chainedSeqRule = calloc(v->chainedSeqRuleCount, sizeof(*v->chainedSeqRule));
+		for(int i = 0; i < v->chainedSeqRuleCount; i++){
+			if(v->chainedSeqRuleOffsets[i] == 0)
+				continue;
+			if(otfpushrange(o, v->chainedSeqRuleOffsets[i], -1))
+				goto err;
+			int r = read_ChainedSequenceRule(o, v->chainedSeqRule+i);
+			if(otfpoprange(o) < 0)
+				goto err;
+			if(r < 0){
+				memset(v->chainedSeqRule+i, 0, sizeof(*v->chainedSeqRule));
+				break;
+			}
+		}
+	}
+	return 0;
+err:
+	werrstr("%s: %r", "ChainedSequenceRuleSet");
+	return -1;
+}
+
+void
+print_ChainedSequenceRuleSet(Otfile *f, int indent, Otf *o, ChainedSequenceRuleSet *v)
+{
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "chainedSeqRuleCount", v->chainedSeqRuleCount);
+	for(int i = 0; i < v->chainedSeqRuleCount; i++)
+		f->print(f->aux, "%*s%s[%d]: %ud\n", indent, "", "chainedSeqRuleOffsets", i, v->chainedSeqRuleOffsets[i]);
+	for(int i = 0; i < v->chainedSeqRuleCount; i++){
+		f->print(f->aux, "%*s%s[%d]:\n", indent, "", "chainedSeqRule", i);
+		print_ChainedSequenceRule(f, indent+indentΔ, o, &v->chainedSeqRule[i]);
+	}
+	USED(o);
+}
+
+int
+read_ChainedSequenceContextFormat1(Otf *o, ChainedSequenceContextFormat1 *v)
+{
+	u8int *b = nil; USED(b);
+	if((b = otfreadn(o, 4)) == nil)
+		goto err;
+	v->coverageOffset = b[0]<<8 | b[1];
+	v->chainedSeqRuleSetCount = b[2]<<8 | b[3];
+	if((b = otfreadn(o, v->chainedSeqRuleSetCount*2)) == nil)
+		goto err;
+	v->chainedSeqRuleSetOffsets = malloc(v->chainedSeqRuleSetCount*sizeof(*v->chainedSeqRuleSetOffsets));
+	for(int i = 0; i < v->chainedSeqRuleSetCount; i++)
+		v->chainedSeqRuleSetOffsets[i] = b[0+i*2]<<8 | b[1+i*2];
+	if(v->chainedSeqRuleSetCount > 0){
+		v->chainedSeqRuleSet = calloc(v->chainedSeqRuleSetCount, sizeof(*v->chainedSeqRuleSet));
+		for(int i = 0; i < v->chainedSeqRuleSetCount; i++){
+			if(v->chainedSeqRuleSetOffsets[i] == 0)
+				continue;
+			if(otfpushrange(o, v->chainedSeqRuleSetOffsets[i], -1))
+				goto err;
+			int r = read_ChainedSequenceRuleSet(o, v->chainedSeqRuleSet+i);
+			if(otfpoprange(o) < 0)
+				goto err;
+			if(r < 0){
+				memset(v->chainedSeqRuleSet+i, 0, sizeof(*v->chainedSeqRuleSet));
+				break;
+			}
+		}
+	}
+	return 0;
+err:
+	werrstr("%s: %r", "ChainedSequenceContextFormat1");
+	return -1;
+}
+
+void
+print_ChainedSequenceContextFormat1(Otfile *f, int indent, Otf *o, ChainedSequenceContextFormat1 *v)
+{
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "coverageOffset", v->coverageOffset);
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "chainedSeqRuleSetCount", v->chainedSeqRuleSetCount);
+	for(int i = 0; i < v->chainedSeqRuleSetCount; i++)
+		f->print(f->aux, "%*s%s[%d]: %ud\n", indent, "", "chainedSeqRuleSetOffsets", i, v->chainedSeqRuleSetOffsets[i]);
+	for(int i = 0; i < v->chainedSeqRuleSetCount; i++){
+		f->print(f->aux, "%*s%s[%d]:\n", indent, "", "chainedSeqRuleSet", i);
+		print_ChainedSequenceRuleSet(f, indent+indentΔ, o, &v->chainedSeqRuleSet[i]);
+	}
+	USED(o);
+}
+
+int
+read_ChainedClassSequenceRuleSet(Otf *o, ChainedClassSequenceRuleSet *v)
+{
+	u8int *b = nil; USED(b);
+	if((b = otfreadn(o, 2)) == nil)
+		goto err;
+	v->chainedClassSeqRuleCount = b[0]<<8 | b[1];
+	if((b = otfreadn(o, v->chainedClassSeqRuleCount*2)) == nil)
+		goto err;
+	v->chainedClassSeqRuleOffsets = malloc(v->chainedClassSeqRuleCount*sizeof(*v->chainedClassSeqRuleOffsets));
+	for(int i = 0; i < v->chainedClassSeqRuleCount; i++)
+		v->chainedClassSeqRuleOffsets[i] = b[0+i*2]<<8 | b[1+i*2];
+	if(v->chainedClassSeqRuleCount > 0){
+		v->chainedClassSeqRules = calloc(v->chainedClassSeqRuleCount, sizeof(*v->chainedClassSeqRules));
+		for(int i = 0; i < v->chainedClassSeqRuleCount; i++){
+			if(v->chainedClassSeqRuleOffsets[i] == 0)
+				continue;
+			if(otfpushrange(o, v->chainedClassSeqRuleOffsets[i], -1))
+				goto err;
+			int r = read_ChainedSequenceRule(o, v->chainedClassSeqRules+i);
+			if(otfpoprange(o) < 0)
+				goto err;
+			if(r < 0){
+				memset(v->chainedClassSeqRules+i, 0, sizeof(*v->chainedClassSeqRules));
+				break;
+			}
+		}
+	}
+	return 0;
+err:
+	werrstr("%s: %r", "ChainedClassSequenceRuleSet");
+	return -1;
+}
+
+void
+print_ChainedClassSequenceRuleSet(Otfile *f, int indent, Otf *o, ChainedClassSequenceRuleSet *v)
+{
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "chainedClassSeqRuleCount", v->chainedClassSeqRuleCount);
+	for(int i = 0; i < v->chainedClassSeqRuleCount; i++)
+		f->print(f->aux, "%*s%s[%d]: %ud\n", indent, "", "chainedClassSeqRuleOffsets", i, v->chainedClassSeqRuleOffsets[i]);
+	for(int i = 0; i < v->chainedClassSeqRuleCount; i++){
+		f->print(f->aux, "%*s%s[%d]:\n", indent, "", "chainedClassSeqRules", i);
+		print_ChainedSequenceRule(f, indent+indentΔ, o, &v->chainedClassSeqRules[i]);
+	}
+	USED(o);
+}
+
+int
+read_ChainedSequenceContextFormat2(Otf *o, ChainedSequenceContextFormat2 *v)
+{
+	u8int *b = nil; USED(b);
+	if((b = otfreadn(o, 10)) == nil)
+		goto err;
+	v->coverageOffset = b[0]<<8 | b[1];
+	v->backtrackClassDefOffset = b[2]<<8 | b[3];
+	v->inputClassDefOffset = b[4]<<8 | b[5];
+	v->lookaheadClassDefOffset = b[6]<<8 | b[7];
+	v->chainedClassSeqRuleSetCount = b[8]<<8 | b[9];
+	if((b = otfreadn(o, v->chainedClassSeqRuleSetCount*2)) == nil)
+		goto err;
+	v->chainedClassSeqRuleSetOffsets = malloc(v->chainedClassSeqRuleSetCount*sizeof(*v->chainedClassSeqRuleSetOffsets));
+	for(int i = 0; i < v->chainedClassSeqRuleSetCount; i++)
+		v->chainedClassSeqRuleSetOffsets[i] = b[0+i*2]<<8 | b[1+i*2];
+	if(v->chainedClassSeqRuleSetCount > 0){
+		v->chainedClassSequenceRuleSets = calloc(v->chainedClassSeqRuleSetCount, sizeof(*v->chainedClassSequenceRuleSets));
+		for(int i = 0; i < v->chainedClassSeqRuleSetCount; i++){
+			if(v->chainedClassSeqRuleSetOffsets[i] == 0)
+				continue;
+			if(otfpushrange(o, v->chainedClassSeqRuleSetOffsets[i], -1))
+				goto err;
+			int r = read_ChainedClassSequenceRuleSet(o, v->chainedClassSequenceRuleSets+i);
+			if(otfpoprange(o) < 0)
+				goto err;
+			if(r < 0){
+				memset(v->chainedClassSequenceRuleSets+i, 0, sizeof(*v->chainedClassSequenceRuleSets));
+				break;
+			}
+		}
+	}
+	return 0;
+err:
+	werrstr("%s: %r", "ChainedSequenceContextFormat2");
+	return -1;
+}
+
+void
+print_ChainedSequenceContextFormat2(Otfile *f, int indent, Otf *o, ChainedSequenceContextFormat2 *v)
+{
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "coverageOffset", v->coverageOffset);
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "backtrackClassDefOffset", v->backtrackClassDefOffset);
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "inputClassDefOffset", v->inputClassDefOffset);
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "lookaheadClassDefOffset", v->lookaheadClassDefOffset);
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "chainedClassSeqRuleSetCount", v->chainedClassSeqRuleSetCount);
+	for(int i = 0; i < v->chainedClassSeqRuleSetCount; i++)
+		f->print(f->aux, "%*s%s[%d]: %ud\n", indent, "", "chainedClassSeqRuleSetOffsets", i, v->chainedClassSeqRuleSetOffsets[i]);
+	for(int i = 0; i < v->chainedClassSeqRuleSetCount; i++){
+		f->print(f->aux, "%*s%s[%d]:\n", indent, "", "chainedClassSequenceRuleSets", i);
+		print_ChainedClassSequenceRuleSet(f, indent+indentΔ, o, &v->chainedClassSequenceRuleSets[i]);
+	}
+	USED(o);
+}
+
+int
+read_ChainedSequenceContextFormat3(Otf *o, ChainedSequenceContextFormat3 *v)
+{
+	u8int *b = nil; USED(b);
+	if((b = otfreadn(o, 2)) == nil)
+		goto err;
+	v->backtrackGlyphCount = b[0]<<8 | b[1];
+	if((b = otfreadn(o, v->backtrackGlyphCount*2)) == nil)
+		goto err;
+	v->backtrackCoverageOffsets = malloc(v->backtrackGlyphCount*sizeof(*v->backtrackCoverageOffsets));
+	for(int i = 0; i < v->backtrackGlyphCount; i++)
+		v->backtrackCoverageOffsets[i] = b[0+i*2]<<8 | b[1+i*2];
+	if((b = otfreadn(o, 2)) == nil)
+		goto err;
+	v->inputGlyphCount = b[0]<<8 | b[1];
+	if((b = otfreadn(o, v->inputGlyphCount*2)) == nil)
+		goto err;
+	v->inputCoverageOffsets = malloc(v->inputGlyphCount*sizeof(*v->inputCoverageOffsets));
+	for(int i = 0; i < v->inputGlyphCount; i++)
+		v->inputCoverageOffsets[i] = b[0+i*2]<<8 | b[1+i*2];
+	if((b = otfreadn(o, 2)) == nil)
+		goto err;
+	v->lookaheadGlyphCount = b[0]<<8 | b[1];
+	if((b = otfreadn(o, v->lookaheadGlyphCount*2)) == nil)
+		goto err;
+	v->lookaheadCoverageOffsets = malloc(v->lookaheadGlyphCount*sizeof(*v->lookaheadCoverageOffsets));
+	for(int i = 0; i < v->lookaheadGlyphCount; i++)
+		v->lookaheadCoverageOffsets[i] = b[0+i*2]<<8 | b[1+i*2];
+	if((b = otfreadn(o, 2)) == nil)
+		goto err;
+	v->seqLookupCount = b[0]<<8 | b[1];
+	if(otfarray(o, &v->seqLookupRecords, read_SequenceLookup, sizeof(SequenceLookup), v->seqLookupCount) < 0){
+		werrstr("%s: %r", "seqLookupRecords");
+		goto err;
+	}
+	return 0;
+err:
+	werrstr("%s: %r", "ChainedSequenceContextFormat3");
+	return -1;
+}
+
+void
+print_ChainedSequenceContextFormat3(Otfile *f, int indent, Otf *o, ChainedSequenceContextFormat3 *v)
+{
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "backtrackGlyphCount", v->backtrackGlyphCount);
+	for(int i = 0; i < v->backtrackGlyphCount; i++)
+		f->print(f->aux, "%*s%s[%d]: %ud\n", indent, "", "backtrackCoverageOffsets", i, v->backtrackCoverageOffsets[i]);
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "inputGlyphCount", v->inputGlyphCount);
+	for(int i = 0; i < v->inputGlyphCount; i++)
+		f->print(f->aux, "%*s%s[%d]: %ud\n", indent, "", "inputCoverageOffsets", i, v->inputCoverageOffsets[i]);
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "lookaheadGlyphCount", v->lookaheadGlyphCount);
+	for(int i = 0; i < v->lookaheadGlyphCount; i++)
+		f->print(f->aux, "%*s%s[%d]: %ud\n", indent, "", "lookaheadCoverageOffsets", i, v->lookaheadCoverageOffsets[i]);
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "seqLookupCount", v->seqLookupCount);
+	for(int i = 0; i < v->seqLookupCount; i++){
+		f->print(f->aux, "%*s%s[%d]:\n", indent, "", "seqLookupRecords", i);
+		print_SequenceLookup(f, indent+indentΔ, o, &v->seqLookupRecords[i]);
+	}
+	USED(o);
+}
+
+int
+read_ChainedSequenceContext(Otf *o, ChainedSequenceContext *v)
+{
+	u8int *b = nil; USED(b);
+	if((b = otfreadn(o, 2)) == nil)
+		goto err;
+	v->format = b[0]<<8 | b[1];
+	if(v->format == 1){
+		if(read_ChainedSequenceContextFormat1(o, &v->f1) < 0){
+			werrstr("%s: %r", "f1");
+			goto err;
+		}
+	}
+	if(v->format == 2){
+		if(read_ChainedSequenceContextFormat2(o, &v->f2) < 0){
+			werrstr("%s: %r", "f2");
+			goto err;
+		}
+	}
+	if(v->format == 3){
+		if(read_ChainedSequenceContextFormat3(o, &v->f3) < 0){
+			werrstr("%s: %r", "f3");
+			goto err;
+		}
+	}
+	return 0;
+err:
+	werrstr("%s: %r", "ChainedSequenceContext");
+	return -1;
+}
+
+void
+print_ChainedSequenceContext(Otfile *f, int indent, Otf *o, ChainedSequenceContext *v)
+{
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "format", v->format);
+	if(v->format == 1){
+		f->print(f->aux, "%*s%s:\n", indent, "", "f1");
+		print_ChainedSequenceContextFormat1(f, indent+indentΔ, o, &v->f1);
+	}
+	if(v->format == 2){
+		f->print(f->aux, "%*s%s:\n", indent, "", "f2");
+		print_ChainedSequenceContextFormat2(f, indent+indentΔ, o, &v->f2);
+	}
+	if(v->format == 3){
+		f->print(f->aux, "%*s%s:\n", indent, "", "f3");
+		print_ChainedSequenceContextFormat3(f, indent+indentΔ, o, &v->f3);
+	}
+	USED(o);
+}
+
+int
+read_PairValue(Otf *o, PairValue *v)
+{
+	u8int *b = nil; USED(b);
+	if((b = otfreadn(o, 2)) == nil)
+		goto err;
+	v->secondGlyph = b[0]<<8 | b[1];
+	if(read_ValueRecord(o, &v->valueRecord1) < 0){
+		werrstr("%s: %r", "valueRecord1");
+		goto err;
+	}
+	if(read_ValueRecord(o, &v->valueRecord2) < 0){
+		werrstr("%s: %r", "valueRecord2");
+		goto err;
+	}
+	return 0;
+err:
+	werrstr("%s: %r", "PairValue");
+	return -1;
+}
+
+void
+print_PairValue(Otfile *f, int indent, Otf *o, PairValue *v)
+{
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "secondGlyph", v->secondGlyph);
+	f->print(f->aux, "%*s%s:\n", indent, "", "valueRecord1");
+	print_ValueRecord(f, indent+indentΔ, o, &v->valueRecord1);
+	f->print(f->aux, "%*s%s:\n", indent, "", "valueRecord2");
+	print_ValueRecord(f, indent+indentΔ, o, &v->valueRecord2);
+	USED(o);
+}
+
+int
+read_PairSet(Otf *o, PairSet *v)
+{
+	u8int *b = nil; USED(b);
+	if((b = otfreadn(o, 2)) == nil)
+		goto err;
+	v->pairValueCount = b[0]<<8 | b[1];
+	if(otfarray(o, &v->pairValueRecords, read_PairValue, sizeof(PairValue), v->pairValueCount) < 0){
+		werrstr("%s: %r", "pairValueRecords");
+		goto err;
+	}
+	return 0;
+err:
+	werrstr("%s: %r", "PairSet");
+	return -1;
+}
+
+void
+print_PairSet(Otfile *f, int indent, Otf *o, PairSet *v)
+{
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "pairValueCount", v->pairValueCount);
+	for(int i = 0; i < v->pairValueCount; i++){
+		f->print(f->aux, "%*s%s[%d]:\n", indent, "", "pairValueRecords", i);
+		print_PairValue(f, indent+indentΔ, o, &v->pairValueRecords[i]);
+	}
+	USED(o);
+}
+
+int
+read_PairPosFormat1(Otf *o, PairPosFormat1 *v)
+{
+	u8int *b = nil; USED(b);
+	if((b = otfreadn(o, 2)) == nil)
+		goto err;
+	v->pairSetCount = b[0]<<8 | b[1];
+	if((b = otfreadn(o, v->pairSetCount*2)) == nil)
+		goto err;
+	v->pairSetOffsets = malloc(v->pairSetCount*sizeof(*v->pairSetOffsets));
+	for(int i = 0; i < v->pairSetCount; i++)
+		v->pairSetOffsets[i] = b[0+i*2]<<8 | b[1+i*2];
+	if(v->pairSetCount > 0){
+		v->pairSets = calloc(v->pairSetCount, sizeof(*v->pairSets));
+		for(int i = 0; i < v->pairSetCount; i++){
+			if(v->pairSetOffsets[i] == 0)
+				continue;
+			if(otfpushrange(o, v->pairSetOffsets[i], -1))
+				goto err;
+			int r = read_PairSet(o, v->pairSets+i);
+			if(otfpoprange(o) < 0)
+				goto err;
+			if(r < 0){
+				memset(v->pairSets+i, 0, sizeof(*v->pairSets));
+				break;
+			}
+		}
+	}
+	return 0;
+err:
+	werrstr("%s: %r", "PairPosFormat1");
+	return -1;
+}
+
+void
+print_PairPosFormat1(Otfile *f, int indent, Otf *o, PairPosFormat1 *v)
+{
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "pairSetCount", v->pairSetCount);
+	for(int i = 0; i < v->pairSetCount; i++)
+		f->print(f->aux, "%*s%s[%d]: %ud\n", indent, "", "pairSetOffsets", i, v->pairSetOffsets[i]);
+	for(int i = 0; i < v->pairSetCount; i++){
+		f->print(f->aux, "%*s%s[%d]:\n", indent, "", "pairSets", i);
+		print_PairSet(f, indent+indentΔ, o, &v->pairSets[i]);
+	}
+	USED(o);
+}
+
+int
+read_Class2(Otf *o, Class2 *v)
+{
+	u8int *b = nil; USED(b);
+	if(read_ValueRecord(o, &v->valueRecord1) < 0){
+		werrstr("%s: %r", "valueRecord1");
+		goto err;
+	}
+	if(read_ValueRecord(o, &v->valueRecord2) < 0){
+		werrstr("%s: %r", "valueRecord2");
+		goto err;
+	}
+	return 0;
+err:
+	werrstr("%s: %r", "Class2");
+	return -1;
+}
+
+void
+print_Class2(Otfile *f, int indent, Otf *o, Class2 *v)
+{
+	f->print(f->aux, "%*s%s:\n", indent, "", "valueRecord1");
+	print_ValueRecord(f, indent+indentΔ, o, &v->valueRecord1);
+	f->print(f->aux, "%*s%s:\n", indent, "", "valueRecord2");
+	print_ValueRecord(f, indent+indentΔ, o, &v->valueRecord2);
+	USED(o);
+}
+
+int
+read_Class1(Otf *o, Class1 *v)
+{
+	u8int *b = nil; USED(b);
+	if(otfarray(o, &v->class2Records, read_Class2, sizeof(Class2), o->class2Count) < 0){
+		werrstr("%s: %r", "class2Records");
+		goto err;
+	}
+	return 0;
+err:
+	werrstr("%s: %r", "Class1");
+	return -1;
+}
+
+void
+print_Class1(Otfile *f, int indent, Otf *o, Class1 *v)
+{
+	for(int i = 0; i < o->class2Count; i++){
+		f->print(f->aux, "%*s%s[%d]:\n", indent, "", "class2Records", i);
+		print_Class2(f, indent+indentΔ, o, &v->class2Records[i]);
+	}
+	USED(o);
+}
+
+int
+read_PairPosFormat2(Otf *o, PairPosFormat2 *v)
+{
+	u8int *b = nil; USED(b);
+	if((b = otfreadn(o, 8)) == nil)
+		goto err;
+	v->classDef1Offset = b[0]<<8 | b[1];
+	v->classDef2Offset = b[2]<<8 | b[3];
+	v->class1Count = b[4]<<8 | b[5];
+	v->class2Count = b[6]<<8 | b[7];
+	o->class2Count = v->class2Count;
+	if(otfarray(o, &v->class1Records, read_Class1, sizeof(Class1), v->class1Count) < 0){
+		werrstr("%s: %r", "class1Records");
+		goto err;
+	}
+	return 0;
+err:
+	werrstr("%s: %r", "PairPosFormat2");
+	return -1;
+}
+
+void
+print_PairPosFormat2(Otfile *f, int indent, Otf *o, PairPosFormat2 *v)
+{
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "classDef1Offset", v->classDef1Offset);
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "classDef2Offset", v->classDef2Offset);
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "class1Count", v->class1Count);
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "class2Count", v->class2Count);
+	o->class2Count = v->class2Count;
+	for(int i = 0; i < v->class1Count; i++){
+		f->print(f->aux, "%*s%s[%d]:\n", indent, "", "class1Records", i);
+		print_Class1(f, indent+indentΔ, o, &v->class1Records[i]);
+	}
+	USED(o);
+}
+
+int
+read_PairPos(Otf *o, PairPos *v)
+{
+	u8int *b = nil; USED(b);
+	if((b = otfreadn(o, 8)) == nil)
+		goto err;
+	v->format = b[0]<<8 | b[1];
+	if(v->format != 1 && v->format != 2){
+		werrstr("%s: invalid value: %d (%#ux)", "format", v->format, v->format);
+		goto err;
+	}
+	v->coverageOffset = b[2]<<8 | b[3];
+	v->valueFormat1 = b[4]<<8 | b[5];
+	v->valueFormat2 = b[6]<<8 | b[7];
+	if(v->format == 1){
+		if(read_PairPosFormat1(o, &v->f1) < 0){
+			werrstr("%s: %r", "f1");
+			goto err;
+		}
+	}
+	if(v->format == 2){
+		if(read_PairPosFormat2(o, &v->f2) < 0){
+			werrstr("%s: %r", "f2");
+			goto err;
+		}
+	}
+	return 0;
+err:
+	werrstr("%s: %r", "PairPos");
+	return -1;
+}
+
+void
+print_PairPos(Otfile *f, int indent, Otf *o, PairPos *v)
+{
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "format", v->format);
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "coverageOffset", v->coverageOffset);
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "valueFormat1", v->valueFormat1);
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "valueFormat2", v->valueFormat2);
+	if(v->format == 1){
+		f->print(f->aux, "%*s%s:\n", indent, "", "f1");
+		print_PairPosFormat1(f, indent+indentΔ, o, &v->f1);
+	}
+	if(v->format == 2){
+		f->print(f->aux, "%*s%s:\n", indent, "", "f2");
+		print_PairPosFormat2(f, indent+indentΔ, o, &v->f2);
+	}
+	USED(o);
+}
+
+int
+read_EntryExit(Otf *o, EntryExit *v)
+{
+	u8int *b = nil; USED(b);
+	if((b = otfreadn(o, 4)) == nil)
+		goto err;
+	v->entryAnchorOffset = b[0]<<8 | b[1];
+	v->exitAnchorOffset = b[2]<<8 | b[3];
+	return 0;
+err:
+	werrstr("%s: %r", "EntryExit");
+	return -1;
+}
+
+void
+print_EntryExit(Otfile *f, int indent, Otf *o, EntryExit *v)
+{
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "entryAnchorOffset", v->entryAnchorOffset);
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "exitAnchorOffset", v->exitAnchorOffset);
+	USED(o);
+}
+
+int
+read_CursivePos(Otf *o, CursivePos *v)
+{
+	u8int *b = nil; USED(b);
+	if((b = otfreadn(o, 6)) == nil)
+		goto err;
+	u16int format = b[0]<<8 | b[1];
+	if(format != 1){
+		werrstr("%s: invalid value: %d (%#ux)", "format", format, format);
+		goto err;
+	}
+	v->coverageOffset = b[2]<<8 | b[3];
+	v->entryExitCount = b[4]<<8 | b[5];
+	if(otfarray(o, &v->entryExitRecords, read_EntryExit, sizeof(EntryExit), v->entryExitCount) < 0){
+		werrstr("%s: %r", "entryExitRecords");
+		goto err;
+	}
+	return 0;
+err:
+	werrstr("%s: %r", "CursivePos");
+	return -1;
+}
+
+void
+print_CursivePos(Otfile *f, int indent, Otf *o, CursivePos *v)
+{
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "coverageOffset", v->coverageOffset);
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "entryExitCount", v->entryExitCount);
+	for(int i = 0; i < v->entryExitCount; i++){
+		f->print(f->aux, "%*s%s[%d]:\n", indent, "", "entryExitRecords", i);
+		print_EntryExit(f, indent+indentΔ, o, &v->entryExitRecords[i]);
+	}
+	USED(o);
+}
+
+int
 read_Lookup(Otf *o, Lookup *v)
 {
 	u8int *b = nil; USED(b);
@@ -3495,6 +4189,78 @@
 	if((b = otfreadn(o, 2)) == nil)
 		goto err;
 	v->markFilteringSet = b[0]<<8 | b[1];
+	if((v->lookupType==LOOKUP_TYPE_SINGLE_ADJUSTMENT)){
+		if(v->subTableCount > 0){
+			v->singleAdjustment = calloc(v->subTableCount, sizeof(*v->singleAdjustment));
+			for(int i = 0; i < v->subTableCount; i++){
+				if(v->subtableOffsets[i] == 0)
+					continue;
+				if(otfpushrange(o, v->subtableOffsets[i], -1))
+					goto err;
+				int r = read_SinglePos(o, v->singleAdjustment+i);
+				if(otfpoprange(o) < 0)
+					goto err;
+				if(r < 0){
+					memset(v->singleAdjustment+i, 0, sizeof(*v->singleAdjustment));
+					break;
+				}
+			}
+		}
+	}
+	if((v->lookupType==LOOKUP_TYPE_PAIR_ADJUSTMENT)){
+		if(v->subTableCount > 0){
+			v->pairAdjustment = calloc(v->subTableCount, sizeof(*v->pairAdjustment));
+			for(int i = 0; i < v->subTableCount; i++){
+				if(v->subtableOffsets[i] == 0)
+					continue;
+				if(otfpushrange(o, v->subtableOffsets[i], -1))
+					goto err;
+				int r = read_PairPos(o, v->pairAdjustment+i);
+				if(otfpoprange(o) < 0)
+					goto err;
+				if(r < 0){
+					memset(v->pairAdjustment+i, 0, sizeof(*v->pairAdjustment));
+					break;
+				}
+			}
+		}
+	}
+	if((v->lookupType==LOOKUP_TYPE_CURSIVE_ATTACHMENT)){
+		if(v->subTableCount > 0){
+			v->cursiveAttachment = calloc(v->subTableCount, sizeof(*v->cursiveAttachment));
+			for(int i = 0; i < v->subTableCount; i++){
+				if(v->subtableOffsets[i] == 0)
+					continue;
+				if(otfpushrange(o, v->subtableOffsets[i], -1))
+					goto err;
+				int r = read_CursivePos(o, v->cursiveAttachment+i);
+				if(otfpoprange(o) < 0)
+					goto err;
+				if(r < 0){
+					memset(v->cursiveAttachment+i, 0, sizeof(*v->cursiveAttachment));
+					break;
+				}
+			}
+		}
+	}
+	if((v->lookupType==LOOKUP_TYPE_CHAINED_CONTEXTS_POSITIONING)){
+		if(v->subTableCount > 0){
+			v->chainedContextsPositioning = calloc(v->subTableCount, sizeof(*v->chainedContextsPositioning));
+			for(int i = 0; i < v->subTableCount; i++){
+				if(v->subtableOffsets[i] == 0)
+					continue;
+				if(otfpushrange(o, v->subtableOffsets[i], -1))
+					goto err;
+				int r = read_ChainedSequenceContext(o, v->chainedContextsPositioning+i);
+				if(otfpoprange(o) < 0)
+					goto err;
+				if(r < 0){
+					memset(v->chainedContextsPositioning+i, 0, sizeof(*v->chainedContextsPositioning));
+					break;
+				}
+			}
+		}
+	}
 	return 0;
 err:
 	werrstr("%s: %r", "Lookup");
@@ -3510,6 +4276,30 @@
 	for(int i = 0; i < v->subTableCount; i++)
 		f->print(f->aux, "%*s%s[%d]: %ud\n", indent, "", "subtableOffsets", i, v->subtableOffsets[i]);
 	f->print(f->aux, "%*s%s: %ud\n", indent, "", "markFilteringSet", v->markFilteringSet);
+	if((v->lookupType==LOOKUP_TYPE_SINGLE_ADJUSTMENT)){
+		for(int i = 0; i < v->subTableCount; i++){
+			f->print(f->aux, "%*s%s[%d]:\n", indent, "", "singleAdjustment", i);
+			print_SinglePos(f, indent+indentΔ, o, &v->singleAdjustment[i]);
+		}
+	}
+	if((v->lookupType==LOOKUP_TYPE_PAIR_ADJUSTMENT)){
+		for(int i = 0; i < v->subTableCount; i++){
+			f->print(f->aux, "%*s%s[%d]:\n", indent, "", "pairAdjustment", i);
+			print_PairPos(f, indent+indentΔ, o, &v->pairAdjustment[i]);
+		}
+	}
+	if((v->lookupType==LOOKUP_TYPE_CURSIVE_ATTACHMENT)){
+		for(int i = 0; i < v->subTableCount; i++){
+			f->print(f->aux, "%*s%s[%d]:\n", indent, "", "cursiveAttachment", i);
+			print_CursivePos(f, indent+indentΔ, o, &v->cursiveAttachment[i]);
+		}
+	}
+	if((v->lookupType==LOOKUP_TYPE_CHAINED_CONTEXTS_POSITIONING)){
+		for(int i = 0; i < v->subTableCount; i++){
+			f->print(f->aux, "%*s%s[%d]:\n", indent, "", "chainedContextsPositioning", i);
+			print_ChainedSequenceContext(f, indent+indentΔ, o, &v->chainedContextsPositioning[i]);
+		}
+	}
 	USED(o);
 }
 
--- a/plan9/otf.h
+++ b/plan9/otf.h
@@ -116,24 +116,53 @@
 void otfprint(Otf *o, Otfile *out, int indent);
 void otfclose(Otf *o);
 
-/* FIXME these might go eventually */
+/*
+ * FIXME this API *will* change, maybe not even once.
+ * atm it's just to get things going, for a start.
+ */
 
 typedef struct Glyf Glyf;
 typedef struct GlyfImage GlyfImage;
+typedef struct RasterOpts RasterOpts;
+typedef struct SubPx SubPx;
 
+enum {
+	GLYF_FMT_GREY_8,
+	GLYF_FMT_RGB_24,
+};
+
+enum {
+	LCD_RGB,
+};
+
 struct GlyfImage {
 	int w;
 	int h;
 	int baseline;
+	int fmt;
 	u8int b[];
 };
 
+struct SubPx {
+	struct {
+		u8int *c;
+		int nc;
+	}fir;
+	int lcd;
+};
+
+struct RasterOpts {
+	double ppemX;
+	double ppemY;
+	SubPx *subpx;
+};
+
 int otfglyfnum(Otf *o);
 int otfrune2glyph(Otf *o, Rune r);
 Rune otfglyph2rune(Otf *o, int g);
 
-Glyf *otfglyf(Otf *o, int index, double ppemX, double ppemY);
-GlyfImage *otfdrawglyf(Otf *o, Glyf *g, double ppemX, double ppemY);
+Glyf *otfglyf(Otf *o, int index, RasterOpts *opts);
+GlyfImage *otfdrawglyf(Otf *o, Glyf *g, RasterOpts *opts);
 
 typedef struct SubHeader SubHeader;
 typedef struct MapGroup MapGroup;
@@ -194,6 +223,23 @@
 typedef struct Feature Feature;
 typedef struct FeatureRecord FeatureRecord;
 typedef struct FeatureList FeatureList;
+typedef struct SequenceLookup SequenceLookup;
+typedef struct ChainedSequenceRule ChainedSequenceRule;
+typedef struct ChainedSequenceRuleSet ChainedSequenceRuleSet;
+typedef struct ChainedSequenceContextFormat1 ChainedSequenceContextFormat1;
+typedef struct ChainedClassSequenceRuleSet ChainedClassSequenceRuleSet;
+typedef struct ChainedSequenceContextFormat2 ChainedSequenceContextFormat2;
+typedef struct ChainedSequenceContextFormat3 ChainedSequenceContextFormat3;
+typedef struct ChainedSequenceContext ChainedSequenceContext;
+typedef struct PairValue PairValue;
+typedef struct PairSet PairSet;
+typedef struct PairPosFormat1 PairPosFormat1;
+typedef struct Class2 Class2;
+typedef struct Class1 Class1;
+typedef struct PairPosFormat2 PairPosFormat2;
+typedef struct PairPos PairPos;
+typedef struct EntryExit EntryExit;
+typedef struct CursivePos CursivePos;
 typedef struct Lookup Lookup;
 typedef struct LookupList LookupList;
 typedef struct TableGPOS TableGPOS;
@@ -918,6 +964,175 @@
 int read_FeatureList(Otf *o, FeatureList *v);
 void print_FeatureList(Otfile *f, int indent, Otf *o, FeatureList *v);
 
+struct SequenceLookup {
+	u16int sequenceIndex;
+	u16int lookupListIndex;
+};
+
+int read_SequenceLookup(Otf *o, SequenceLookup *v);
+void print_SequenceLookup(Otfile *f, int indent, Otf *o, SequenceLookup *v);
+
+struct ChainedSequenceRule {
+	u16int backtrackGlyphCount;
+	u16int *backtrackSequence;
+	u16int inputGlyphCount;
+	u16int *inputSequence;
+	u16int lookaheadGlyphCount;
+	u16int *lookaheadSequence;
+	u16int seqLookupCount;
+	SequenceLookup *seqLookupRecords;
+};
+
+int read_ChainedSequenceRule(Otf *o, ChainedSequenceRule *v);
+void print_ChainedSequenceRule(Otfile *f, int indent, Otf *o, ChainedSequenceRule *v);
+
+struct ChainedSequenceRuleSet {
+	u16int chainedSeqRuleCount;
+	u16int *chainedSeqRuleOffsets;
+	ChainedSequenceRule *chainedSeqRule;
+};
+
+int read_ChainedSequenceRuleSet(Otf *o, ChainedSequenceRuleSet *v);
+void print_ChainedSequenceRuleSet(Otfile *f, int indent, Otf *o, ChainedSequenceRuleSet *v);
+
+struct ChainedSequenceContextFormat1 {
+	u16int coverageOffset;
+	u16int chainedSeqRuleSetCount;
+	u16int *chainedSeqRuleSetOffsets;
+	ChainedSequenceRuleSet *chainedSeqRuleSet;
+};
+
+int read_ChainedSequenceContextFormat1(Otf *o, ChainedSequenceContextFormat1 *v);
+void print_ChainedSequenceContextFormat1(Otfile *f, int indent, Otf *o, ChainedSequenceContextFormat1 *v);
+
+struct ChainedClassSequenceRuleSet {
+	u16int chainedClassSeqRuleCount;
+	u16int *chainedClassSeqRuleOffsets;
+	ChainedSequenceRule *chainedClassSeqRules;
+};
+
+int read_ChainedClassSequenceRuleSet(Otf *o, ChainedClassSequenceRuleSet *v);
+void print_ChainedClassSequenceRuleSet(Otfile *f, int indent, Otf *o, ChainedClassSequenceRuleSet *v);
+
+struct ChainedSequenceContextFormat2 {
+	u16int coverageOffset;
+	u16int backtrackClassDefOffset;
+	u16int inputClassDefOffset;
+	u16int lookaheadClassDefOffset;
+	u16int chainedClassSeqRuleSetCount;
+	u16int *chainedClassSeqRuleSetOffsets;
+	ChainedClassSequenceRuleSet *chainedClassSequenceRuleSets;
+};
+
+int read_ChainedSequenceContextFormat2(Otf *o, ChainedSequenceContextFormat2 *v);
+void print_ChainedSequenceContextFormat2(Otfile *f, int indent, Otf *o, ChainedSequenceContextFormat2 *v);
+
+struct ChainedSequenceContextFormat3 {
+	u16int backtrackGlyphCount;
+	u16int *backtrackCoverageOffsets;
+	u16int inputGlyphCount;
+	u16int *inputCoverageOffsets;
+	u16int lookaheadGlyphCount;
+	u16int *lookaheadCoverageOffsets;
+	u16int seqLookupCount;
+	SequenceLookup *seqLookupRecords;
+};
+
+int read_ChainedSequenceContextFormat3(Otf *o, ChainedSequenceContextFormat3 *v);
+void print_ChainedSequenceContextFormat3(Otfile *f, int indent, Otf *o, ChainedSequenceContextFormat3 *v);
+
+struct ChainedSequenceContext {
+	u16int format;
+	ChainedSequenceContextFormat1 f1;
+	ChainedSequenceContextFormat2 f2;
+	ChainedSequenceContextFormat3 f3;
+};
+
+int read_ChainedSequenceContext(Otf *o, ChainedSequenceContext *v);
+void print_ChainedSequenceContext(Otfile *f, int indent, Otf *o, ChainedSequenceContext *v);
+
+struct PairValue {
+	u16int secondGlyph;
+	ValueRecord valueRecord1;
+	ValueRecord valueRecord2;
+};
+
+int read_PairValue(Otf *o, PairValue *v);
+void print_PairValue(Otfile *f, int indent, Otf *o, PairValue *v);
+
+struct PairSet {
+	u16int pairValueCount;
+	PairValue *pairValueRecords;
+};
+
+int read_PairSet(Otf *o, PairSet *v);
+void print_PairSet(Otfile *f, int indent, Otf *o, PairSet *v);
+
+struct PairPosFormat1 {
+	u16int pairSetCount;
+	u16int *pairSetOffsets;
+	PairSet *pairSets;
+};
+
+int read_PairPosFormat1(Otf *o, PairPosFormat1 *v);
+void print_PairPosFormat1(Otfile *f, int indent, Otf *o, PairPosFormat1 *v);
+
+struct Class2 {
+	ValueRecord valueRecord1;
+	ValueRecord valueRecord2;
+};
+
+int read_Class2(Otf *o, Class2 *v);
+void print_Class2(Otfile *f, int indent, Otf *o, Class2 *v);
+
+struct Class1 {
+	Class2 *class2Records;
+};
+
+int read_Class1(Otf *o, Class1 *v);
+void print_Class1(Otfile *f, int indent, Otf *o, Class1 *v);
+
+struct PairPosFormat2 {
+	u16int classDef1Offset;
+	u16int classDef2Offset;
+	u16int class1Count;
+	u16int class2Count;
+	Class1 *class1Records;
+};
+
+int read_PairPosFormat2(Otf *o, PairPosFormat2 *v);
+void print_PairPosFormat2(Otfile *f, int indent, Otf *o, PairPosFormat2 *v);
+
+struct PairPos {
+	u16int format;
+	u16int coverageOffset;
+	u16int valueFormat1;
+	u16int valueFormat2;
+	PairPosFormat1 f1;
+	PairPosFormat2 f2;
+};
+
+int read_PairPos(Otf *o, PairPos *v);
+void print_PairPos(Otfile *f, int indent, Otf *o, PairPos *v);
+
+struct EntryExit {
+	u16int entryAnchorOffset;
+	u16int exitAnchorOffset;
+};
+
+int read_EntryExit(Otf *o, EntryExit *v);
+void print_EntryExit(Otfile *f, int indent, Otf *o, EntryExit *v);
+
+struct CursivePos {
+	// u16int format;
+	u16int coverageOffset;
+	u16int entryExitCount;
+	EntryExit *entryExitRecords;
+};
+
+int read_CursivePos(Otf *o, CursivePos *v);
+void print_CursivePos(Otfile *f, int indent, Otf *o, CursivePos *v);
+
 enum { // Lookup
 	// lookupFlag
 	LOOKUP_FL_RIGHT_TO_LEFT = 1<<0,
@@ -943,6 +1158,10 @@
 	u16int subTableCount;
 	u16int *subtableOffsets;
 	u16int markFilteringSet;
+	SinglePos *singleAdjustment;
+	PairPos *pairAdjustment;
+	CursivePos *cursiveAttachment;
+	ChainedSequenceContext *chainedContextsPositioning;
 };
 
 int read_Lookup(Otf *o, Lookup *v);
--- a/plan9/otfpriv.h
+++ b/plan9/otfpriv.h
@@ -21,6 +21,7 @@
 	u16int storageOffset;
 	u16int firstGlyphIndex;
 	u16int lastGlyphIndex;
+	u16int class2Count;
 	u16int axisCount;
 	u16int instanceCount;
 	u16int instanceSize;
--- a/rast.c
+++ b/rast.c
@@ -333,9 +333,9 @@
 }
 
 static int
-qbzr(SegQ *seg, int ns, int dw, int dh, u8int *p)
+qbzr(SegQ *seg, int ns, int dw, int dh, int pitch, u8int *p)
 {
-	int ju², j², w, h, i, j, ju, px, py;
+	int ju², j², w, h, i, j, ju, px, py, v;
 	Sval rju², s₀, s, *c, *c₀x, *c₀y, *cs, zx, zy;
 	u64int *ma, *mb, all, nall;
 
@@ -451,9 +451,10 @@
 				}
 				c = c₀x + 3*j²*j²;
 			}
-			*p++ = 255 -
-				(s < QBZR_PIX_SCALE/255 ? 0 : (s >= QBZR_PIX_SCALE ? 255 : s/QBZR_PIX_SCALE*255));
+			v = s/QBZR_PIX_SCALE*255;
+			*p++ = v < 0 ? 0 : (v > 255 ? 255 : v);
 		}
+		p += pitch - dw;
 	}
 	free(ma);
 	return 0;
@@ -547,10 +548,31 @@
 	return npts < 2 ? 0 : npts;
 }
 
+static void
+subpxfir(GlyfImage *im, SubPx *sub)
+{
+	u8int *in, *o;
+	int x, y, i, a;
+
+	o = im->b + im->w*im->h;
+	for(y = 0; y < im->h; y++){
+		in = im->b + y*im->w;
+		for(x = 0; x < im->w; x++){
+			a = 0;
+			for(i = 0; i < sub->fir.nc; i++){
+				if(x >= i)
+					a += sub->fir.c[i]*in[x-i]/255;
+			}
+			o[x] = a > 255 ? 255 : a;
+		}
+		memcpy(in, o, im->w);
+	}
+}
+
 static GlyfImage *
-outlineglyf(Otf *o, Glyf *g, double ppemX, double ppemY)
+outlineglyf(Otf *o, Glyf *g, RasterOpts *opts)
 {
-	int i, j, maxptstotal, maxpts, ngs, w, h, npx, baseline;
+	int i, j, maxptstotal, maxpts, ngs, rw, rh, w, h, npx, baseline;
 	Sval bb[4], offY, scaleX, scaleY;
 	Glyf *gs[MAXCOMPONENTS] = {nil};
 	ComponentGlyph *cg;
@@ -566,7 +588,7 @@
 		gs[ngs++] = g;
 	}else{
 		for(cg = g->component; cg != nil && ngs < nelem(gs); cg = cg->next, ngs++){
-			if((gs[ngs] = otfglyf(o, cg->glyphIndex, ppemX, ppemY)) == nil)
+			if((gs[ngs] = otfglyf(o, cg->glyphIndex, opts)) == nil)
 				goto done;
 		}
 	}
@@ -588,8 +610,10 @@
 */
 
 	pts = calloc(1, maxpts*sizeof(*pts) + maxptstotal/2*sizeof(*s));
-	scaleX = ppemX / o->td.head->unitsPerEm;
-	scaleY = ppemY / o->td.head->unitsPerEm;
+	scaleX = opts->ppemX / o->td.head->unitsPerEm;
+	scaleY = opts->ppemY / o->td.head->unitsPerEm;
+	if(opts->subpx != nil)
+		scaleX *= 3;
 	bb[0] = g->xMin*scaleX;
 	bb[1] = g->yMin*scaleY;
 	bb[2] = g->xMax*scaleX;
@@ -646,7 +670,7 @@
 			bb[3] += 8*ε;
 		}
 		bb[3] += offY;
-		w = ceil(bb[2] - bb[0]);
+		w = ceil(bb[2]+8*ε) - floor(bb[0]-4*ε);
 		h = ceil(bb[3]);
 
 		for(p = s₀; p != s; p++){
@@ -658,14 +682,28 @@
 			p->p2.y += offY;
 		}
 	}
+	rw = w;
+	rh = h;
+	if(opts->subpx != nil){
+		rw += 2;
+		rh++;
+		if((rw % 3) != 0)
+			rw += 3 - (rw % 3);
+	}
 
-	npx = w*h;
-	im = malloc(sizeof(*im) + npx);
-	im->b[0] = 0;
-	if(qbzr(s₀, s-s₀, w, h, im->b) == 0){
-		im->w = w;
+	npx = rw*rh;
+	im = calloc(npx, sizeof(*im));
+	if(qbzr(s₀, s-s₀, w, h, rw, im->b) == 0){
+		im->w = rw;
 		im->h = h;
 		im->baseline = baseline;
+		if(opts->subpx != nil){
+			subpxfir(im, opts->subpx);
+			im->w /= 3;
+			im->fmt = GLYF_FMT_RGB_24;
+		}else{
+			im->fmt = GLYF_FMT_GREY_8;
+		}
 	}else{
 		free(im);
 		im = nil;
@@ -689,11 +727,11 @@
 		if(bg->format == 2 || bg->format == 5){
 			i = y*bg->pitchBits + x;
 			x = i - (i & ~7);
-			return (bg->image[i>>3] & (0x80>>x)) ? 255 : 0;
+			return (bg->image[i>>3] & (0x80>>x)) ? 0 : 255;
 		}
 		i = y*bg->pitchBits+x;
 		x &= 7;
-		return (bg->image[i>>3] & (0x80>>x)) ? 255 : 0;
+		return (bg->image[i>>3] & (0x80>>x)) ? 0 : 255;
 	}else if(bg->bitDepth == 4){
 		/* FIXME */
 		assert(nil);
@@ -733,9 +771,9 @@
 }
 
 GlyfImage *
-bitmapglyf(Otf *o, Glyf *g, double ppemX, double ppemY)
+bitmapglyf(Otf *o, Glyf *g, RasterOpts *opts)
 {
-	int zoomX, zoomY, n, w, h, x, y, i, bb[4], ngs;
+	int zoomX, zoomY, w, h, x, y, i, bb[4], ngs;
 	Glyf *gs[MAXCOMPONENTS], *cg;
 	EbdtComponent *c;
 	BitmapGlyph *bg;
@@ -744,9 +782,9 @@
 	USED(o);
 	im = nil;
 	bg = g->bitmap;
-	if((zoomX = ppemX/bg->ppemX) < 1)
+	if((zoomX = opts->ppemX/bg->ppemX) < 1)
 		zoomX = 1;
-	if((zoomY = ppemY/bg->ppemY) < 1)
+	if((zoomY = opts->ppemY/bg->ppemY) < 1)
 		zoomY = 1;
 
 	bb[0] = g->xMin;
@@ -755,7 +793,7 @@
 	bb[3] = g->yMax;
 	for(ngs = 0; ngs < nelem(gs) && ngs < bg->numComponents;){
 		c = bg->components + ngs;
-		if((cg = gs[ngs] = otfglyf(o, c->glyphID, ppemX, ppemY)) == nil)
+		if((cg = gs[ngs] = otfglyf(o, c->glyphID, opts)) == nil)
 			goto err;
 		ngs++;
 		if(cg->type != GLYPH_BITMAP){
@@ -783,10 +821,6 @@
 	bitblit(g, im, zoomX, zoomY, bb);
 	for(i = 0; i < ngs; i++)
 		bitblit(gs[i], im, zoomX, zoomY, bb);
-	n = im->w*im->h;
-	for(i = 0; i < n; i++)
-		im->b[i] = 255 - im->b[i];
-
 	for(i = 0; i < ngs; i++){
 		free(gs[i]->bitmap);
 		free(gs[i]);
@@ -803,12 +837,12 @@
 }
 
 GlyfImage *
-otfdrawglyf(Otf *o, Glyf *g, double ppemX, double ppemY)
+otfdrawglyf(Otf *o, Glyf *g, RasterOpts *opts)
 {
 	if(g->type == GLYPH_SIMPLE || g->type == GLYPH_COMPONENT)
-		return outlineglyf(o, g, ppemX, ppemY);
+		return outlineglyf(o, g, opts);
 	if(g->type == GLYPH_BITMAP)
-		return bitmapglyf(o, g, ppemX, ppemY);
+		return bitmapglyf(o, g, opts);
 	werrstr("empty glyph");
 	return nil;
 }
--- a/test.h
+++ b/test.h
@@ -12,12 +12,21 @@
 	f->print(f->aux, " -H: highlight a specific glyph in the map by its ID\n");
 	f->print(f->aux, " -x: horizontal ppem\n");
 	f->print(f->aux, " -y: vertical ppem\n");
+	f->print(f->aux, " -s: use subpixel rendering if applicable\n");
 	f->print(f->aux, "Specifying -m more than once adds guide lines\n");
 	f->print(f->aux, "Specifying -m more than twice makes empty pixels filled out\n");
 }
 
+static u8int deffir[] = {8, 77, 86, 77, 8};
+static RasterOpts ropts;
+static SubPx subpx = {
+	.lcd = LCD_RGB,
+	.fir = {
+		.c = deffir,
+		.nc = nelem(deffir),
+	},
+};
 static int gind = -1, map, highlight = -1, runesonly;
-static double ppemX, ppemY;
 static Rune rune = NoRune;
 static char *gtypes[] = {
 	[GLYPH_EMPTY] = "empty",
@@ -91,12 +100,21 @@
 			for(d = 0; d < im[i]->h; d++, lt += bw*3){
 				for(t = 0; t < im[i]->w; t++){
 					u8int r, g, b;
-					r = g = b = im[i]->b[d*im[i]->w + t];
+					u32int rgb;
+					if(im[i]->fmt == GLYF_FMT_RGB_24){
+						r = im[i]->b[(d*im[i]->w + t)*3 + 0];
+						g = im[i]->b[(d*im[i]->w + t)*3 + 1];
+						b = im[i]->b[(d*im[i]->w + t)*3 + 2];
+					}else{
+						r = g = b = im[i]->b[d*im[i]->w + t];
+					}
+					rgb = r<<16 | g<<8 | b;
 					border = d == 0 || t == 0 || t == im[i]->w-1 || d == im[i]->h-1;
-					if((lines || highlight == i) && g == 0xff && (d == im[i]->h+im[i]->baseline)){
+					if((lines || highlight == i) && rgb == 0 && (d == im[i]->h+im[i]->baseline)){
+						r = 0xff;
 						g = 0;
 						b = 0;
-					}else if((lines || highlight == i) && g == 0xff && (fill || border)){
+					}else if((lines || highlight == i) && rgb == 0 && (fill || border)){
 						if(fill && !border){
 							r = 0xff;
 							g = 0x80;
@@ -103,8 +121,13 @@
 							b = 0xff;
 						}else if(border){
 							r = 0;
+							g = 0xff;
 							b = 0;
 						}
+					}else{
+						r = 0xff - r;
+						g = 0xff - g;
+						b = 0xff - b;
 					}
 					if(r != 0xff || g != 0xff || b != 0xff){ 
 						lt[t*3+0] = b;
@@ -147,28 +170,28 @@
 		goto glypherr;
 	}
 	if(map && gind < 0){
-		im = ppemX > 0 ? calloc(n, sizeof(*im)) : nil;
+		im = ropts.ppemX > 0 ? calloc(n, sizeof(*im)) : nil;
 		for(i = 0; i < n; i++){
 			if(runesonly && otfglyph2rune(o, i) == NoRune)
 				continue;
-			if((g = otfglyf(o, i, ppemX, ppemY)) == nil){
+			if((g = otfglyf(o, i, &ropts)) == nil){
 				gind = i;
 glypherr:
 				werrstr("glyph %d: %r", gind);
 				return -1;
 			}
-			if(ppemX > 0 && g->type != GLYPH_EMPTY){
-				if((im[i] = otfdrawglyf(o, g, ppemX, ppemY)) == nil){
+			if(ropts.ppemX > 0 && g->type != GLYPH_EMPTY){
+				if((im[i] = otfdrawglyf(o, g, &ropts)) == nil){
 					gind = i;
 					goto glypherr;
 				}
-			}else if(ppemX <= 0){
+			}else if(ropts.ppemX <= 0){
 				out->print(out->aux, "%d (%s):\n", i, gtypes[g->type]);
 				print_Glyf(out, indentΔ, o, g);
 			}
 			free(g);
 		}
-		if(ppemX > 0){
+		if(ropts.ppemX > 0){
 			if(out->write != otfdiscard && dumpmap(out, im, n) != 0)
 				return -1;
 			for(i = 0; i < n; i++)
@@ -178,11 +201,11 @@
 	}else if(gind < 0){
 		otfprint(o, out, indentΔ);
 	}else{
-		if((g = otfglyf(o, gind, ppemX, ppemY)) == nil){
+		if((g = otfglyf(o, gind, &ropts)) == nil){
 			goto glypherr;
-		}else if(ppemX > 0){
+		}else if(ropts.ppemX > 0){
 			GlyfImage *im;
-			if((im = otfdrawglyf(o, g, ppemX, ppemY)) == nil)
+			if((im = otfdrawglyf(o, g, &ropts)) == nil)
 				goto glypherr;
 			if(out->write != otfdiscard && dumpmap(out, &im, 1) != 0)
 				return -1;
@@ -213,7 +236,7 @@
 		map++; \
 		break; \
 	case 'p': \
-		ppemX = ppemY = strtod(EARGF(usage(&out)), nil); \
+		ropts.ppemX = ropts.ppemY = strtod(EARGF(usage(&out)), nil); \
 		break; \
 	case 'r': { \
 		char *s = EARGF(usage(&out)); \
@@ -229,11 +252,14 @@
 	case 'R': \
 		runesonly++; \
 		break; \
+	case 's': \
+		ropts.subpx = &subpx; \
+		break; \
 	case 'x': \
-		ppemX = strtod(EARGF(usage(&out)), nil); \
+		ropts.ppemX = strtod(EARGF(usage(&out)), nil); \
 		break; \
 	case 'y': \
-		ppemY = strtod(EARGF(usage(&out)), nil); \
+		ropts.ppemY = strtod(EARGF(usage(&out)), nil); \
 		break; \
 	default: \
 		usage(&out); \
@@ -240,7 +266,7 @@
 	}ARGEND \
 	if(argc != 1) \
 		usage(&out); \
-	if(ppemX == 0 && ppemY != 0) \
-		ppemX = ppemY; \
-	if(ppemY == 0 && ppemX != 0) \
-		ppemY = ppemX;
+	if(ropts.ppemX == 0 && ropts.ppemY != 0) \
+		ropts.ppemX = ropts.ppemY; \
+	if(ropts.ppemY == 0 && ropts.ppemX != 0) \
+		ropts.ppemY = ropts.ppemX;