2e5a07597bfa95dedb669fef2a5e2b8048f2c644 angie Mon Dec 5 08:08:23 2011 -0800 Feature #3711 (VCF haplo-sorting display): Implemented Richard Durbin'ssuggestions as new defaults: 1. Instead of ref=blue, alt=red, simply ignore ref and draw only alt, in black. Draw top & bottom bounds of variants in extra pixel rows. 2. Draw the tree using open triangles instead of open rectangles. diff --git src/hg/hgTracks/vcfTrack.c src/hg/hgTracks/vcfTrack.c index 7b2d5dc..5456212 100644 --- src/hg/hgTracks/vcfTrack.c +++ src/hg/hgTracks/vcfTrack.c @@ -1,23 +1,24 @@ /* vcfTrack -- handlers for Variant Call Format data. */ #include "common.h" #include "bigWarn.h" #include "dystring.h" #include "errCatch.h" #include "hacTree.h" #include "hdb.h" +#include "hgColors.h" #include "hgTracks.h" #include "pgSnp.h" #include "trashDir.h" #include "vcf.h" #include "vcfUi.h" #if (defined USE_TABIX && defined KNETFILE_HOOKS) #include "knetUdc.h" #include "udc.h" #endif//def USE_TABIX && KNETFILE_HOOKS #ifdef USE_TABIX static boolean getMinQual(struct trackDb *tdb, double *retMinQual) /* Return TRUE and set retMinQual if cart contains minimum QUAL filter */ { @@ -503,130 +504,198 @@ } dyStringPrintf(dy, "%s/%s:%d %s/%s:%d %s/%s:%d", rec->alleles[0], rec->alleles[0], gtRefRefCount, rec->alleles[0], rec->alleles[1], gtRefAltCount, rec->alleles[1], rec->alleles[1], gtAltAltCount); if (gtOtherCount > 0) dyStringPrintf(dy, " other:%d", gtOtherCount); // Restore original values of pooled strings. if (revCmplDisp) { for (i=0; i < rec->alleleCount; i++) reverseComplement(rec->alleles[i], strlen(rec->alleles[i])); } return dy->string; } -// This is initialized when we start drawing: -static Color purple = 0; - void mapBoxForCenterVariant(struct vcfRecord *rec, struct hvGfx *hvg, struct track *tg, int xOff, int yOff, int width) /* Special mouseover for center variant */ { static struct dyString *dy = NULL; if (dy == NULL) dy = dyStringNew(0); dyStringClear(dy); dyStringPrintf(dy, "%s Haplotypes sorted on ", gtSummaryString(rec)); char *centerChrom = cartOptionalStringClosestToHome(cart, tg->tdb, FALSE, "centerVariantChrom"); if (centerChrom == NULL || !sameString(chromName, centerChrom)) dyStringAppend(dy, "middle variant by default. "); else dyStringAppend(dy, "this variant. "); dyStringAppend(dy, "To anchor sorting to a different variant, click on that variant and " "then click on the 'Use this variant' button below the variant name."); const double scale = scaleForPixels(width); int x1 = round((double)(rec->chromStart-winStart)*scale) + xOff; int x2 = round((double)(rec->chromEnd-winStart)*scale) + xOff; int w = x2-x1; if (w <= 1) { x1--; w = 3; } mapBoxHgcOrHgGene(hvg, rec->chromStart, rec->chromEnd, x1, yOff, w, tg->height, tg->track, rec->name, dy->string, NULL, TRUE, NULL); } +// These are initialized when we start drawing, then constant. +static Color purple = 0; +static Color undefYellow = 0; + +enum hapColorMode + { + altOnlyMode, + refAltMode, + baseMode + }; + +static Color colorByAltOnly(int refs, int alts, int unks) +/* Coloring alternate alleles only: shade by proportion of alt alleles to refs, unknowns */ +{ +if (unks > (refs + alts)) + return undefYellow; +int grayIx = hGrayInRange(alts, 0, alts+refs+unks, maxShade+1) - 1; // undo force to 1 +return shadesOfGray[grayIx]; +} + +static Color colorByRefAlt(int refs, int alts, int unks) +/* Color blue for reference allele, red for alternate allele, gray for unknown, purple + * for reasonably mixed. */ +{ +const int fudgeFactor = 4; // Threshold factor for calling one color or the other when mixed +if (unks > (refs + alts)) + return undefYellow; +if (alts > fudgeFactor * refs) + return MG_RED; +if (refs > fudgeFactor * alts) + return MG_BLUE; +return purple; +} + +static Color colorByBase(int refs, int alts, int unks, char *refAl, char *altAl) +/* Color gray for unknown or mixed, otherwise pgSnpColor of predominant allele. */ +{ +const int fudgeFactor = 4; // Threshold for calling for one color or the other when mixed +if (unks > (refs + alts)) + return undefYellow; +if (alts > fudgeFactor * refs) + return pgSnpColor(altAl); +if (refs > fudgeFactor * alts) + return pgSnpColor(refAl); +return shadesOfGray[5]; +} + +// tg->height needs an extra pixel at the bottom; it's eaten by the clipping rectangle: +#define CLIP_PAD 1 + static void drawOneRec(struct vcfRecord *rec, unsigned short *gtHapOrder, unsigned short gtHapCount, struct track *tg, struct hvGfx *hvg, int xOff, int yOff, int width, - boolean isCenter, boolean colorByRefAlt) + boolean isClustered, boolean isCenter, enum hapColorMode colorMode) /* Draw a stack of genotype bars for this record */ { const double scale = scaleForPixels(width); int x1 = round((double)(rec->chromStart-winStart)*scale) + xOff; int x2 = round((double)(rec->chromEnd-winStart)*scale) + xOff; int w = x2-x1; if (w <= 1) { x1--; w = 3; } -double hapsPerPix = (double)gtHapCount / (tg->height-1); +// When coloring mode is altOnly, we draw one extra pixel row at the top & one at bottom +// to show the locations of variants, since the reference alleles are invisible: +int extraPixel = 0; +int hapHeight = tg->height - CLIP_PAD; +if (colorMode == altOnlyMode) + { + hvGfxLine(hvg, x1, yOff, x2, yOff, (isClustered ? purple : shadesOfGray[5])); + extraPixel = 1; + hapHeight -= extraPixel*2; + } +double hapsPerPix = (double)gtHapCount / hapHeight; int pixIx; -for (pixIx = 0; pixIx < tg->height-1; pixIx++) +for (pixIx = 0; pixIx < hapHeight; pixIx++) { int gtHapOrderIxStart = (int)(hapsPerPix * pixIx); int gtHapOrderIxEnd = round(hapsPerPix * (pixIx + 1)); if (gtHapOrderIxEnd == gtHapOrderIxStart) gtHapOrderIxEnd++; int unks = 0, refs = 0, alts = 0; int gtHapOrderIx; for (gtHapOrderIx = gtHapOrderIxStart; gtHapOrderIx < gtHapOrderIxEnd; gtHapOrderIx++) { int gtHapIx = gtHapOrder[gtHapOrderIx]; int hapIx = gtHapIx & 1; int gtIx = gtHapIx >>1; struct vcfGenotype *gt = &(rec->genotypes[gtIx]); if (!gt->isPhased && gt->hapIxA != gt->hapIxB) unks++; else { int alIx = hapIx ? gt->hapIxB : gt->hapIxA; if (alIx) alts++; else refs++; } } - const int fudgeFactor = 4; - Color col = MG_BLACK; - if (unks > (refs + alts)) - col = shadesOfGray[5]; - else if (alts > fudgeFactor * refs) - col = colorByRefAlt ? MG_RED : pgSnpColor(rec->alleles[1]); - else if (refs > fudgeFactor * alts) - col = colorByRefAlt ? MG_BLUE : pgSnpColor(rec->alleles[0]); + int y = yOff + extraPixel + pixIx; + Color col; + if (colorMode == baseMode) + col = colorByBase(refs, alts, unks, rec->alleles[0], rec->alleles[1]); + else if (colorMode == refAltMode) + col = colorByRefAlt(refs, alts, unks); else - col = colorByRefAlt ? purple : shadesOfGray[5]; - int y = yOff + pixIx; + col = colorByAltOnly(refs, alts, unks); + if (col != MG_WHITE) hvGfxLine(hvg, x1, y, x2, y, col); } char *mouseoverText = gtSummaryString(rec); +int yBot = yOff + tg->height - CLIP_PAD - 1; if (isCenter) { + if (colorMode == altOnlyMode) + { + // Colorful outline to distinguish this variant: + hvGfxLine(hvg, x1-1, yOff, x1-1, yBot, purple); + hvGfxLine(hvg, x2+1, yOff, x2+1, yBot, purple); + hvGfxLine(hvg, x1-1, yOff, x2+1, yOff, purple); + hvGfxLine(hvg, x1-1, yBot, x2+1, yBot, purple); + } + else + { // Thick black lines to distinguish this variant: - int yBot = yOff + tg->height - 2; hvGfxBox(hvg, x1-3, yOff, 3, tg->height, MG_BLACK); hvGfxBox(hvg, x2, yOff, 3, tg->height, MG_BLACK); hvGfxLine(hvg, x1-2, yOff, x2+2, yOff, MG_BLACK); hvGfxLine(hvg, x1-2, yBot, x2+2, yBot, MG_BLACK); + } // Mouseover is handled separately by mapBoxForCenterVariant } else mapBoxHgcOrHgGene(hvg, rec->chromStart, rec->chromEnd, x1, yOff, w, tg->height, tg->track, rec->name, mouseoverText, NULL, TRUE, NULL); +if (colorMode == altOnlyMode) + hvGfxLine(hvg, x1, yBot, x2, yBot, (isClustered ? purple : shadesOfGray[5])); } static int getCenterVariantIx(struct track *tg, int seqStart, int seqEnd, struct vcfRecord *records) // If the user hasn't specified a local variant/position to use as center, // just use the median variant in window. { int defaultIx = (slCount(records)-1) / 2; char *centerChrom = cartOptionalStringClosestToHome(cart, tg->tdb, FALSE, "centerVariantChrom"); if (centerChrom != NULL && sameString(chromName, centerChrom)) { int centerPos = cartUsualIntClosestToHome(cart, tg->tdb, FALSE, "centerVariantPos", -1); int winSize = seqEnd - seqStart; if (centerPos > (seqStart - winSize) && centerPos < (seqEnd + winSize)) { @@ -695,98 +764,114 @@ } /* Pixel y offset return type for recursive tree-drawing: */ enum yRetType { yrtMidPoint, yrtStart, yrtEnd, }; /* Callback for calculating y (in pixels) for a cluster node: */ typedef int yFromNodeFunc(const struct slList *itemOrCluster, void *extraData, enum yRetType yType); static int rDrawTreeInLabelArea(struct hacTree *ht, struct hvGfx *hvg, enum yRetType yType, int x, - yFromNodeFunc *yFromNode, void *yh, struct titleHelper *th) + yFromNodeFunc *yFromNode, void *yh, struct titleHelper *th, + boolean drawRectangle) /* Recursively draw the haplotype clustering tree in the left label area. * Returns pixel height for use at non-leaf levels of tree. */ { const int branchW = 4; int labelEnd = leftLabelX + leftLabelWidth; if (yType == yrtStart || yType == yrtEnd) { // We're just getting vertical span of a leaf cluster, not drawing any lines. int yLeft, yRight; if (ht->left) - yLeft = rDrawTreeInLabelArea(ht->left, hvg, yType, x, yFromNode, yh, th); + yLeft = rDrawTreeInLabelArea(ht->left, hvg, yType, x, yFromNode, yh, th, drawRectangle); else yLeft = yFromNode(ht->itemOrCluster, yh, yType); if (ht->right) - yRight = rDrawTreeInLabelArea(ht->right, hvg, yType, x, yFromNode, yh, th); + yRight = rDrawTreeInLabelArea(ht->right, hvg, yType, x, yFromNode, yh, th, drawRectangle); else yRight = yFromNode(ht->itemOrCluster, yh, yType); if (yType == yrtStart) return min(yLeft, yRight); else return max(yLeft, yRight); } // Otherwise yType is yrtMidPoint. If we have 2 children, we'll be drawing some lines: if (ht->left != NULL && ht->right != NULL) { int midY; if (ht->childDistance == 0 || x+(2*branchW) > labelEnd) { // Treat this as a leaf cluster. // Recursing twice is wasteful. Could be avoided if this, and yFromNode, // returned both yStart and yEnd. However, the time to draw a tree of // 2188 hap's (1kG phase1 interim) is in the noise, so I consider it // not worth the effort of refactoring to save a sub-millisecond here. int yStartLeft = rDrawTreeInLabelArea(ht->left, hvg, yrtStart, x+branchW, - yFromNode, yh, th); + yFromNode, yh, th, drawRectangle); int yEndLeft = rDrawTreeInLabelArea(ht->left, hvg, yrtEnd, x+branchW, - yFromNode, yh, th); + yFromNode, yh, th, drawRectangle); int yStartRight = rDrawTreeInLabelArea(ht->right, hvg, yrtStart, x+branchW, - yFromNode, yh, th); + yFromNode, yh, th, drawRectangle); int yEndRight = rDrawTreeInLabelArea(ht->right, hvg, yrtEnd, x+branchW, - yFromNode, yh, th); + yFromNode, yh, th, drawRectangle); int yStart = min(yStartLeft, yStartRight); int yEnd = max(yEndLeft, yEndRight); midY = (yStart + yEnd) / 2; Color col = (ht->childDistance == 0) ? purple : MG_BLACK; + if (drawRectangle) + { hvGfxLine(hvg, x+branchW-1, yStart, x+branchW-1, yEnd-1, col); hvGfxLine(hvg, x+branchW, yStart, labelEnd, yStart, col); hvGfxLine(hvg, x+branchW, yEnd-1, labelEnd, yEnd-1, col); + } + else + { + hvGfxLine(hvg, x, midY, labelEnd, yStart, col); + hvGfxLine(hvg, x, midY, labelEnd, yEnd-1, col); + } addClusterMapItem(ht, x, yStart, labelEnd, yEnd-1, th); } else { int leftMid = rDrawTreeInLabelArea(ht->left, hvg, yrtMidPoint, x+branchW, - yFromNode, yh, th); + yFromNode, yh, th, drawRectangle); int rightMid = rDrawTreeInLabelArea(ht->right, hvg, yrtMidPoint, x+branchW, - yFromNode, yh, th); + yFromNode, yh, th, drawRectangle); midY = (leftMid + rightMid) / 2; + if (drawRectangle) hvGfxLine(hvg, x+branchW-1, leftMid, x+branchW-1, rightMid, MG_BLACK); + else + { + hvGfxLine(hvg, x, midY, x+branchW-1, leftMid, MG_BLACK); + hvGfxLine(hvg, x, midY, x+branchW-1, rightMid, MG_BLACK); + } addClusterMapItem(ht, x, min(leftMid, rightMid), x+branchW-1, max(leftMid, rightMid), th); } + if (drawRectangle) hvGfxLine(hvg, x, midY, x+branchW-1, midY, MG_BLACK); return midY; } else if (ht->left != NULL) - return rDrawTreeInLabelArea(ht->left, hvg, yType, x, yFromNode, yh, th); + return rDrawTreeInLabelArea(ht->left, hvg, yType, x, yFromNode, yh, th, drawRectangle); else if (ht->right != NULL) - return rDrawTreeInLabelArea(ht->right, hvg, yType, x, yFromNode, yh, th); + return rDrawTreeInLabelArea(ht->right, hvg, yType, x, yFromNode, yh, th, drawRectangle); // Leaf node -- return pixel height. Draw a line if yType is midpoint. int y = yFromNode(ht->itemOrCluster, yh, yType); if (yType == yrtMidPoint && x < labelEnd) { hvGfxLine(hvg, x, y, labelEnd, y, purple); addClusterMapItem(ht, x, y, labelEnd, y+1, th); } return y; } struct yFromNodeHelper /* Pre-computed mapping from cluster nodes' gtHapIx to pixel heights. */ { unsigned short gtHapCount; unsigned short *gtHapIxToPxStart; @@ -845,127 +930,156 @@ int len = endIx - startIx; AllocArray(th->refs, len); AllocArray(th->alts, len); struct vcfRecord *rec; int i; for (rec = vcff->records, i = 0; rec != NULL && i < endIx; rec = rec->next, i++) { if (i < startIx) continue; th->refs[i-startIx] = rec->alleles[0]; th->alts[i-startIx] = cloneString(rec->alleles[1]); tolowers(th->alts[i-startIx]); } } -static void drawTreeInLabelArea(struct hacTree *ht, struct hvGfx *hvg, int yOff, int height, - struct yFromNodeHelper *yHelper, struct titleHelper *titleHelper) +static void drawTreeInLabelArea(struct hacTree *ht, struct hvGfx *hvg, int yOff, int clipHeight, + struct yFromNodeHelper *yHelper, struct titleHelper *titleHelper, + boolean drawRectangle) /* Draw the haplotype clustering in the left label area (as much as fits there). */ { // Figure out which hvg to use, save current clipping, and clip to left label coords: struct hvGfx *hvgLL = (hvgSide != NULL) ? hvgSide : hvg; int clipXBak, clipYBak, clipWidthBak, clipHeightBak; hvGfxGetClip(hvgLL, &clipXBak, &clipYBak, &clipWidthBak, &clipHeightBak); hvGfxUnclip(hvgLL); -hvGfxSetClip(hvgLL, leftLabelX, yOff, leftLabelWidth, height); +hvGfxSetClip(hvgLL, leftLabelX, yOff, leftLabelWidth, clipHeight); // Draw the tree: int x = leftLabelX; -(void)rDrawTreeInLabelArea(ht, hvgLL, yrtMidPoint, x, yFromHapNode, yHelper, titleHelper); +(void)rDrawTreeInLabelArea(ht, hvgLL, yrtMidPoint, x, yFromHapNode, yHelper, titleHelper, + drawRectangle); // Restore the prior clipping: hvGfxUnclip(hvgLL); hvGfxSetClip(hvgLL, clipXBak, clipYBak, clipWidthBak, clipHeightBak); } static void ignoreEm(char *format, va_list args) /* Ignore warnings from genotype parsing -- when there's one, there * are usually hundreds more just like it. */ { } +static enum hapColorMode getColorMode(struct trackDb *tdb) +/* Get the hap-cluster coloring mode from cart & tdb. */ +{ +enum hapColorMode colorMode = altOnlyMode; +char *colorBy = cartUsualStringClosestToHome(cart, tdb, FALSE, + VCF_HAP_COLORBY_VAR, VCF_DEFAULT_HAP_COLORBY); +if (sameString(colorBy, VCF_HAP_COLORBY_ALTONLY)) + colorMode = altOnlyMode; +else if (sameString(colorBy, VCF_HAP_COLORBY_REFALT)) + colorMode = refAltMode; +else if (sameString(colorBy, VCF_HAP_COLORBY_BASE)) + colorMode = baseMode; +return colorMode; +} + static void vcfHapClusterDraw(struct track *tg, int seqStart, int seqEnd, struct hvGfx *hvg, int xOff, int yOff, int width, MgFont *font, Color color, enum trackVisibility vis) /* Split samples' chromosomes (haplotypes), cluster them by center-weighted * alpha similarity, and draw in the order determined by clustering. */ { const struct vcfFile *vcff = tg->extraUiData; if (vcff->records == NULL) return; purple = hvGfxFindColorIx(hvg, 0x99, 0x00, 0xcc); -char *colorBy = cartUsualStringClosestToHome(cart, tg->tdb, FALSE, - VCF_HAP_COLORBY_VAR, VCF_HAP_COLORBY_REFALT); -boolean colorByRefAlt = sameString(colorBy, VCF_HAP_COLORBY_REFALT); +undefYellow = hvGfxFindRgb(hvg, &undefinedYellowColor); +enum hapColorMode colorMode = getColorMode(tg->tdb); pushWarnHandler(ignoreEm); struct vcfRecord *rec; for (rec = vcff->records; rec != NULL; rec = rec->next) vcfParseGenotypes(rec); popWarnHandler(); unsigned short gtHapCount = 0; int nRecords = slCount(vcff->records); int centerIx = getCenterVariantIx(tg, seqStart, seqEnd, vcff->records); // Limit the number of variants that we compare, to keep from timing out: // (really what we should limit is the number of distinct haplo's passed to hacTree!) -const int maxVariantsPerSide = 20; +// In the meantime, this should at least be a cart var... +int maxVariantsPerSide = 50; int startIx = max(0, centerIx - maxVariantsPerSide); int endIx = min(nRecords, centerIx+1 + maxVariantsPerSide); struct hacTree *ht = NULL; unsigned short *gtHapOrder = clusterHaps(vcff, centerIx, startIx, endIx, >HapCount, &ht); struct vcfRecord *centerRec = NULL; int ix; // Unlike drawing order (last drawn is on top), the first mapBox gets priority, // so map center variant before drawing & mapping other variants! for (rec = vcff->records, ix=0; rec != NULL; rec = rec->next, ix++) { if (ix == centerIx) { centerRec = rec; mapBoxForCenterVariant(rec, hvg, tg, xOff, yOff, width); break; } } for (rec = vcff->records, ix=0; rec != NULL; rec = rec->next, ix++) { + boolean isClustered = (ix >= startIx && ix < endIx); if (ix != centerIx) - drawOneRec(rec, gtHapOrder, gtHapCount, tg, hvg, xOff, yOff, width, FALSE, colorByRefAlt); + drawOneRec(rec, gtHapOrder, gtHapCount, tg, hvg, xOff, yOff, width, isClustered, FALSE, + colorMode); } // Draw the center rec on top, outlined with black lines, to make sure it is very visible: -drawOneRec(centerRec, gtHapOrder, gtHapCount, tg, hvg, xOff, yOff, width, TRUE, colorByRefAlt); +drawOneRec(centerRec, gtHapOrder, gtHapCount, tg, hvg, xOff, yOff, width, TRUE, TRUE, + colorMode); // Draw as much of the tree as can fit in the left label area: +int extraPixel = (colorMode == altOnlyMode) ? 1 : 0; +int hapHeight = tg->height- CLIP_PAD - 2*extraPixel; struct yFromNodeHelper yHelper = {0, NULL, NULL}; -initYFromNodeHelper(&yHelper, yOff, tg->height-1, gtHapCount, gtHapOrder); +initYFromNodeHelper(&yHelper, yOff+extraPixel, hapHeight, gtHapCount, gtHapOrder); struct titleHelper titleHelper = { NULL, 0, 0, 0, 0, NULL, NULL }; initTitleHelper(&titleHelper, tg->track, startIx, centerIx, endIx, nRecords, vcff); -drawTreeInLabelArea(ht, hvg, yOff, tg->height, &yHelper, &titleHelper); +char *treeAngle = cartUsualStringClosestToHome(cart, tg->tdb, FALSE, VCF_HAP_TREEANGLE_VAR, + VCF_DEFAULT_HAP_TREEANGLE); +boolean drawRectangle = sameString(treeAngle, VCF_HAP_TREEANGLE_RECTANGLE); +drawTreeInLabelArea(ht, hvg, yOff+extraPixel, hapHeight+CLIP_PAD, &yHelper, &titleHelper, + drawRectangle); } static int vcfHapClusterTotalHeight(struct track *tg, enum trackVisibility vis) /* Return height of haplotype graph (2 * #samples * lineHeight); * 2 because we're assuming diploid genomes here, no XXY, tetraploid etc. */ { -// Should we make it single-height when on chrY? const struct vcfFile *vcff = tg->extraUiData; if (vcff->records == NULL) return 0; int ploidy = sameString(chromName, "chrY") ? 1 : 2; int simpleHeight = ploidy * vcff->genotypeCount * tg->lineHeight; int defaultHeight = min(simpleHeight, VCF_DEFAULT_HAP_HEIGHT); char *tdbHeight = trackDbSettingOrDefault(tg->tdb, VCF_HAP_HEIGHT_VAR, NULL); if (isNotEmpty(tdbHeight)) defaultHeight = atoi(tdbHeight); int cartHeight = cartUsualIntClosestToHome(cart, tg->tdb, FALSE, VCF_HAP_HEIGHT_VAR, defaultHeight); -tg->height = min(cartHeight+1, maximumTrackHeight(tg)); +if (tg->visibility == tvSquish) + cartHeight /= 2; +int extraPixel = (getColorMode(tg->tdb) == altOnlyMode) ? 1 : 0; +int totalHeight = cartHeight + CLIP_PAD + 2*extraPixel; +tg->height = min(totalHeight, maximumTrackHeight(tg)); return tg->height; } static char *vcfHapClusterTrackName(struct track *tg, void *item) /* If someone asks for itemName/mapItemName, just send name of track like wiggle. */ { return tg->track; } static void vcfHapClusterOverloadMethods(struct track *tg, struct vcfFile *vcff) /* If we confirm at load time that we can draw a haplotype graph, use * this to overwrite the methods for the rest of execution: */ { tg->heightPer = (tg->visibility == tvSquish) ? (tl.fontHeight/4) : (tl.fontHeight / 2); tg->lineHeight = tg->heightPer + 1;