diff options
Diffstat (limited to 'Build/source/texk/web2c/mfluadir/otfcc/lib/table/CFF.c')
-rw-r--r-- | Build/source/texk/web2c/mfluadir/otfcc/lib/table/CFF.c | 1411 |
1 files changed, 1411 insertions, 0 deletions
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/CFF.c b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/CFF.c new file mode 100644 index 00000000000..8ec40f150fe --- /dev/null +++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/CFF.c @@ -0,0 +1,1411 @@ +#include "CFF.h" + +#include "support/util.h" +#include "libcff/libcff.h" +#include "libcff/charstring-il.h" +#include "libcff/subr.h" + + +const double DEFAULT_BLUE_SCALE = 0.039625; +const double DEFAULT_BLUE_SHIFT = 7; +const double DEFAULT_BLUE_FUZZ = 1; +const double DEFAULT_EXPANSION_FACTOR = 0.06; + +static cff_PrivateDict *otfcc_newCff_private() { + cff_PrivateDict *pd = NULL; + NEW(pd); + pd->blueFuzz = DEFAULT_BLUE_FUZZ; + pd->blueScale = DEFAULT_BLUE_SCALE; + pd->blueShift = DEFAULT_BLUE_SHIFT; + pd->expansionFactor = DEFAULT_EXPANSION_FACTOR; + return pd; +} + +static INLINE void initFD(table_CFF *fd) { + memset(fd, 0, sizeof(*fd)); + fd->underlinePosition = -100; + fd->underlineThickness = 50; +} +static void otfcc_delete_privatedict(cff_PrivateDict *priv) { + if (!priv) return; + FREE(priv->blueValues); + FREE(priv->otherBlues); + FREE(priv->familyBlues); + + FREE(priv->familyOtherBlues); + FREE(priv->stemSnapH); + FREE(priv->stemSnapV); + FREE(priv); +} +static INLINE void disposeFontMatrix(cff_FontMatrix *fm) { + if (!fm) return; + iVQ.dispose(&fm->x); + iVQ.dispose(&fm->y); +} +static INLINE void disposeFD(table_CFF *fd) { + sdsfree(fd->version); + sdsfree(fd->notice); + sdsfree(fd->copyright); + sdsfree(fd->fullName); + sdsfree(fd->familyName); + sdsfree(fd->weight); + + sdsfree(fd->fontName); + sdsfree(fd->cidRegistry); + sdsfree(fd->cidOrdering); + disposeFontMatrix(fd->fontMatrix); + FREE(fd->fontMatrix); + otfcc_delete_privatedict(fd->privateDict); + if (fd->fdArray) { + for (tableid_t j = 0; j < fd->fdArrayCount; j++) { + table_iCFF.free(fd->fdArray[j]); + } + FREE(fd->fdArray); + } +} + +caryll_standardRefType(table_CFF, table_iCFF, initFD, disposeFD); + +typedef struct { + int32_t fdArrayIndex; + table_CFF *meta; + table_glyf *glyphs; + cff_File *cffFile; + uint64_t seed; +} cff_extract_context; +static void callback_extract_private(uint32_t op, uint8_t top, cff_Value *stack, void *_context) { + cff_extract_context *context = (cff_extract_context *)_context; + table_CFF *meta = context->meta; + if (context->fdArrayIndex >= 0 && context->fdArrayIndex < meta->fdArrayCount) { + meta = meta->fdArray[context->fdArrayIndex]; + } + cff_PrivateDict *pd = meta->privateDict; + switch (op) { + // DELTAs + case op_BlueValues: { + pd->blueValuesCount = top; + NEW(pd->blueValues, pd->blueValuesCount); + for (arity_t j = 0; j < pd->blueValuesCount; j++) { + pd->blueValues[j] = cffnum(stack[j]); + } + break; + } + case op_OtherBlues: { + pd->otherBluesCount = top; + NEW(pd->otherBlues, pd->otherBluesCount); + for (arity_t j = 0; j < pd->otherBluesCount; j++) { + pd->otherBlues[j] = cffnum(stack[j]); + } + break; + } + case op_FamilyBlues: { + pd->familyBluesCount = top; + NEW(pd->familyBlues, pd->familyBluesCount); + for (arity_t j = 0; j < pd->familyBluesCount; j++) { + pd->familyBlues[j] = cffnum(stack[j]); + } + break; + } + case op_FamilyOtherBlues: { + pd->familyOtherBluesCount = top; + NEW(pd->familyOtherBlues, pd->familyOtherBluesCount); + for (arity_t j = 0; j < pd->familyOtherBluesCount; j++) { + pd->familyOtherBlues[j] = cffnum(stack[j]); + } + break; + } + case op_StemSnapH: { + pd->stemSnapHCount = top; + NEW(pd->stemSnapH, pd->stemSnapHCount); + for (arity_t j = 0; j < pd->stemSnapHCount; j++) { + pd->stemSnapH[j] = cffnum(stack[j]); + } + break; + } + case op_StemSnapV: { + pd->stemSnapVCount = top; + NEW(pd->stemSnapV, pd->stemSnapVCount); + for (arity_t j = 0; j < pd->stemSnapVCount; j++) { + pd->stemSnapV[j] = cffnum(stack[j]); + } + break; + } + // Numbers + case op_BlueScale: + if (top) { pd->blueScale = cffnum(stack[top - 1]); } + break; + case op_BlueShift: + if (top) { pd->blueShift = cffnum(stack[top - 1]); } + break; + case op_BlueFuzz: + if (top) { pd->blueFuzz = cffnum(stack[top - 1]); } + break; + case op_StdHW: + if (top) { pd->stdHW = cffnum(stack[top - 1]); } + break; + case op_StdVW: + if (top) { pd->stdVW = cffnum(stack[top - 1]); } + break; + case op_ForceBold: + if (top) { pd->forceBold = cffnum(stack[top - 1]); } + break; + case op_LanguageGroup: + if (top) { pd->languageGroup = cffnum(stack[top - 1]); } + break; + case op_ExpansionFactor: + if (top) { pd->expansionFactor = cffnum(stack[top - 1]); } + break; + case op_initialRandomSeed: + if (top) { pd->initialRandomSeed = cffnum(stack[top - 1]); } + break; + case op_defaultWidthX: + if (top) { pd->defaultWidthX = cffnum(stack[top - 1]); } + break; + case op_nominalWidthX: + if (top) { pd->nominalWidthX = cffnum(stack[top - 1]); } + break; + } +} + +static void callback_extract_fd(uint32_t op, uint8_t top, cff_Value *stack, void *_context) { + cff_extract_context *context = (cff_extract_context *)_context; + cff_File *file = context->cffFile; + table_CFF *meta = context->meta; + if (context->fdArrayIndex >= 0 && context->fdArrayIndex < meta->fdArrayCount) { + meta = meta->fdArray[context->fdArrayIndex]; + } + switch (op) { + case op_version: + if (top) { meta->version = sdsget_cff_sid(stack[top - 1].i, file->string); } + break; + case op_Notice: + if (top) { meta->notice = sdsget_cff_sid(stack[top - 1].i, file->string); } + break; + case op_Copyright: + if (top) { meta->copyright = sdsget_cff_sid(stack[top - 1].i, file->string); } + break; + case op_FontName: + if (top) { meta->fontName = sdsget_cff_sid(stack[top - 1].i, file->string); } + break; + case op_FullName: + if (top) { meta->fullName = sdsget_cff_sid(stack[top - 1].i, file->string); } + break; + case op_FamilyName: + if (top) { meta->familyName = sdsget_cff_sid(stack[top - 1].i, file->string); } + break; + case op_Weight: + if (top) { meta->weight = sdsget_cff_sid(stack[top - 1].i, file->string); } + break; + case op_FontBBox: + if (top >= 4) { + meta->fontBBoxLeft = cffnum(stack[top - 4]); + meta->fontBBoxBottom = cffnum(stack[top - 3]); + meta->fontBBoxRight = cffnum(stack[top - 2]); + meta->fontBBoxTop = cffnum(stack[top - 1]); + } + break; + case op_FontMatrix: + if (top >= 6) { + NEW(meta->fontMatrix); + meta->fontMatrix->a = cffnum(stack[top - 6]); + meta->fontMatrix->b = cffnum(stack[top - 5]); + meta->fontMatrix->c = cffnum(stack[top - 4]); + meta->fontMatrix->d = cffnum(stack[top - 3]); + meta->fontMatrix->x = iVQ.createStill(cffnum(stack[top - 2])); + meta->fontMatrix->y = iVQ.createStill(cffnum(stack[top - 1])); + } + break; + case op_isFixedPitch: + if (top) { meta->isFixedPitch = (bool)cffnum(stack[top - 1]); } + break; + case op_ItalicAngle: + if (top) { meta->italicAngle = cffnum(stack[top - 1]); } + break; + case op_UnderlinePosition: + if (top) { meta->underlinePosition = cffnum(stack[top - 1]); } + break; + case op_UnderlineThickness: + if (top) { meta->underlineThickness = cffnum(stack[top - 1]); } + break; + case op_StrokeWidth: + if (top) { meta->strokeWidth = cffnum(stack[top - 1]); } + break; + + // Private + case op_Private: + if (top >= 2) { + uint32_t privateLength = cffnum(stack[top - 2]); + uint32_t privateOffset = cffnum(stack[top - 1]); + meta->privateDict = otfcc_newCff_private(); + cff_iDict.parseToCallback(file->raw_data + privateOffset, privateLength, context, + callback_extract_private); + } + break; + // CID + case op_ROS: + if (top >= 3) { + meta->isCID = true; + meta->cidRegistry = sdsget_cff_sid(stack[top - 3].i, file->string); + meta->cidOrdering = sdsget_cff_sid(stack[top - 2].i, file->string); + meta->cidSupplement = cffnum(stack[top - 1]); + } + break; + } +} + +typedef struct { + glyf_Glyph *g; + shapeid_t jContour; + shapeid_t jPoint; + double defaultWidthX; + double nominalWidthX; + uint8_t definedHStems; + uint8_t definedVStems; + uint8_t definedHintMasks; + uint8_t definedContourMasks; + uint64_t randx; +} outline_builder_context; + +static void callback_draw_setwidth(void *_context, double width) { + outline_builder_context *context = (outline_builder_context *)_context; + iVQ.replace(&context->g->advanceWidth, iVQ.createStill(width + context->nominalWidthX)); +} +static void callback_draw_next_contour(void *_context) { + outline_builder_context *context = (outline_builder_context *)_context; + glyf_Contour c; + glyf_iContour.init(&c); + glyf_iContourList.push(&context->g->contours, c); + context->jContour = context->g->contours.length; + context->jPoint = 0; +} +static void callback_draw_lineto(void *_context, double x1, double y1) { + outline_builder_context *context = (outline_builder_context *)_context; + if (context->jContour) { + glyf_Contour *contour = &context->g->contours.items[context->jContour - 1]; + glyf_Point z; + glyf_iPoint.init(&z); + z.onCurve = true; + iVQ.copyReplace(&z.x, iVQ.createStill(x1)); + iVQ.copyReplace(&z.y, iVQ.createStill(y1)); + glyf_iContour.push(contour, z); + context->jPoint += 1; + } +} +static void callback_draw_curveto(void *_context, double x1, double y1, double x2, double y2, + double x3, double y3) { + outline_builder_context *context = (outline_builder_context *)_context; + if (context->jContour) { + glyf_Contour *contour = &context->g->contours.items[context->jContour - 1]; + { + glyf_Point z; + glyf_iPoint.init(&z); + z.onCurve = false; + iVQ.copyReplace(&z.x, iVQ.createStill(x1)); + iVQ.copyReplace(&z.y, iVQ.createStill(y1)); + glyf_iContour.push(contour, z); + } + { + glyf_Point z; + glyf_iPoint.init(&z); + z.onCurve = false; + iVQ.copyReplace(&z.x, iVQ.createStill(x2)); + iVQ.copyReplace(&z.y, iVQ.createStill(y2)); + glyf_iContour.push(contour, z); + } + { + glyf_Point z; + glyf_iPoint.init(&z); + z.onCurve = true; + iVQ.copyReplace(&z.x, iVQ.createStill(x3)); + iVQ.copyReplace(&z.y, iVQ.createStill(y3)); + glyf_iContour.push(contour, z); + } + context->jPoint += 3; + } +} +static void callback_draw_sethint(void *_context, bool isVertical, double position, double width) { + outline_builder_context *context = (outline_builder_context *)_context; + glyf_iStemDefList.push((isVertical ? &context->g->stemV : &context->g->stemH), // + ((glyf_PostscriptStemDef){ + .position = position, + .width = width, + })); +} +static void callback_draw_setmask(void *_context, bool isContourMask, bool *maskArray) { + outline_builder_context *context = (outline_builder_context *)_context; + glyf_MaskList *maskList = isContourMask ? &context->g->contourMasks : &context->g->hintMasks; + glyf_PostscriptHintMask mask; + + if (context->jContour) { + mask.contoursBefore = context->jContour - 1; + } else { + mask.contoursBefore = 0; + } + mask.pointsBefore = context->jPoint; + + for (shapeid_t j = 0; j < 0x100; j++) { + mask.maskH[j] = j < context->g->stemH.length ? maskArray[j] : 0; + mask.maskV[j] = j < context->g->stemV.length ? maskArray[j + context->g->stemH.length] : 0; + } + + FREE(maskArray); + if (maskList->length > 0 && + maskList->items[maskList->length - 1].contoursBefore == mask.contoursBefore && + maskList->items[maskList->length - 1].pointsBefore == mask.pointsBefore) { + // two masks are stacking together + // simply replace maskH and maskV + for (shapeid_t j = 0; j < 0x100; j++) { + maskList->items[maskList->length - 1].maskH[j] = mask.maskH[j]; + maskList->items[maskList->length - 1].maskV[j] = mask.maskV[j]; + } + } else { + glyf_iMaskList.push(maskList, mask); + if (isContourMask) { + context->definedContourMasks += 1; + } else { + context->definedHintMasks += 1; + } + } +} + +static double callback_draw_getrand(void *_context) { + // xorshift64* PRNG to double53 + outline_builder_context *context = (outline_builder_context *)_context; + uint64_t x = context->randx; + x ^= x >> 12; + x ^= x << 25; + x ^= x >> 27; + context->randx = x; + union { + uint64_t u; + double d; + } a; + a.u = x * UINT64_C(2685821657736338717); + a.u = (a.u >> 12) | UINT64_C(0x3FF0000000000000); + double q = (a.u & 2048) ? (1.0 - (2.2204460492503131E-16 / 2.0)) : 1.0; + return a.d - q; +} + +static cff_IOutlineBuilder drawPass = {.setWidth = callback_draw_setwidth, + .newContour = callback_draw_next_contour, + .lineTo = callback_draw_lineto, + .curveTo = callback_draw_curveto, + .setHint = callback_draw_sethint, + .setMask = callback_draw_setmask, + .getrand = callback_draw_getrand}; + +static void buildOutline(glyphid_t i, cff_extract_context *context, const otfcc_Options *options) { + cff_File *f = context->cffFile; + glyf_Glyph *g = otfcc_newGlyf_glyph(); + context->glyphs->items[i] = g; + uint64_t seed = context->seed; + + cff_Index localSubrs; + cff_iIndex.init(&localSubrs); + + cff_Stack stack; + stack.max = 0x10000; + NEW(stack.stack, stack.max); + stack.index = 0; + stack.stem = 0; + + outline_builder_context bc = {g, 0, 0, 0.0, 0.0, 0, 0, 0, 0, 0}; + + // Determine used FD index and subroutine list + uint8_t fd = 0; + if (f->fdselect.t != cff_FDSELECT_UNSPECED) + fd = cff_parseSubr(i, f->raw_data, f->font_dict, f->fdselect, &localSubrs); + else + fd = cff_parseSubr(i, f->raw_data, f->top_dict, f->fdselect, &localSubrs); + + g->fdSelect = Handle.fromIndex(fd); + + // Decide default and nominal width + if (context->meta->fdArray && fd < context->meta->fdArrayCount && + context->meta->fdArray[fd]->privateDict) { + bc.defaultWidthX = context->meta->fdArray[fd]->privateDict->defaultWidthX; + bc.nominalWidthX = context->meta->fdArray[fd]->privateDict->nominalWidthX; + } else if (context->meta->privateDict) { + bc.defaultWidthX = context->meta->privateDict->defaultWidthX; + bc.nominalWidthX = context->meta->privateDict->nominalWidthX; + } + iVQ.replace(&g->advanceWidth, iVQ.createStill(bc.defaultWidthX)); + + // Get charstring + uint8_t *charStringPtr = f->char_strings.data + f->char_strings.offset[i] - 1; + uint32_t charStringLength = f->char_strings.offset[i + 1] - f->char_strings.offset[i]; + + // Draw points + stack.index = 0; + stack.stem = 0; + bc.jContour = 0; + bc.jPoint = 0; + bc.randx = seed; + + cff_parseOutline(charStringPtr, charStringLength, f->global_subr, localSubrs, &stack, &bc, + drawPass, options); + + // Turn deltas into absolute coordinates + VQ cx = iVQ.neutral(), cy = iVQ.neutral(); + for (shapeid_t j = 0; j < g->contours.length; j++) { + glyf_Contour *contour = &g->contours.items[j]; + for (shapeid_t k = 0; k < contour->length; k++) { + glyf_Point *z = &contour->items[k]; + iVQ.inplacePlus(&cx, z->x); + iVQ.inplacePlus(&cy, z->y); + iVQ.copyReplace(&z->x, cx); + iVQ.copyReplace(&z->y, cy); + } + if (!iVQ.compare(contour->items[0].x, contour->items[contour->length - 1].x) && + !iVQ.compare(contour->items[0].y, contour->items[contour->length - 1].y) && + (contour->items[0].onCurve && contour->items[contour->length - 1].onCurve)) { + // We found a duplicate knot at the beginning and end of a curve + // mainly due to curveto. We can remove that. This is unnecessary. + glyf_iContour.pop(contour); + } + glyf_iContour.shrinkToFit(contour); + } + glyf_iContourList.shrinkToFit(&g->contours); + iVQ.dispose(&cx), iVQ.dispose(&cy); + + cff_iIndex.dispose(&localSubrs); + FREE(stack.stack); + context->seed = bc.randx; +} + +static sds formCIDString(cffsid_t cid) { + return sdscatprintf(sdsnew("CID"), "%d", cid); +} + +static void nameGlyphsAccordingToCFF(cff_extract_context *context) { + cff_File *cffFile = context->cffFile; + table_glyf *glyphs = context->glyphs; + cff_Charset *charset = &cffFile->charsets; + if (context->meta->isCID) { + switch (charset->t) { + case cff_CHARSET_FORMAT0: { + for (glyphid_t j = 0; j < charset->s; j++) { + cffsid_t sid = charset->f0.glyph[j]; + sds glyphname = sdsget_cff_sid(sid, cffFile->string); + if (glyphname) { + glyphs->items[j + 1]->name = glyphname; + glyphs->items[j + 1]->cid = sid; + } + } + break; + } + case cff_CHARSET_FORMAT1: { + uint32_t glyphsNamedSofar = 1; + for (glyphid_t j = 0; j < charset->s; j++) { + cffsid_t first = charset->f1.range1[j].first; + for (glyphid_t k = 0; k <= charset->f1.range1[j].nleft; k++) { + cffsid_t sid = first + k; + sds glyphname = formCIDString(sid); + if (glyphsNamedSofar < glyphs->length && glyphname) { + glyphs->items[glyphsNamedSofar]->name = glyphname; + glyphs->items[glyphsNamedSofar]->cid = sid; + } + glyphsNamedSofar++; + } + } + break; + } + case cff_CHARSET_FORMAT2: { + uint32_t glyphsNamedSofar = 1; + for (glyphid_t j = 0; j < charset->s; j++) { + cffsid_t first = charset->f2.range2[j].first; + for (glyphid_t k = 0; k <= charset->f2.range2[j].nleft; k++) { + cffsid_t sid = first + k; + sds glyphname = formCIDString(sid); + if (glyphsNamedSofar < glyphs->length && glyphname) { + glyphs->items[glyphsNamedSofar]->name = glyphname; + glyphs->items[glyphsNamedSofar]->cid = sid; + } + glyphsNamedSofar++; + } + } + break; + } + } + } else { + switch (charset->t) { + case cff_CHARSET_FORMAT0: { + for (glyphid_t j = 0; j < charset->s; j++) { + cffsid_t sid = charset->f0.glyph[j]; + sds glyphname = sdsget_cff_sid(sid, cffFile->string); + if (glyphname) { glyphs->items[j + 1]->name = glyphname; } + } + break; + } + case cff_CHARSET_FORMAT1: { + uint32_t glyphsNamedSofar = 1; + for (glyphid_t j = 0; j < charset->s; j++) { + glyphid_t first = charset->f1.range1[j].first; + for (glyphid_t k = 0; k <= charset->f1.range1[j].nleft; k++) { + cffsid_t sid = first + k; + sds glyphname = sdsget_cff_sid(sid, cffFile->string); + if (glyphsNamedSofar < glyphs->length && glyphname) { + glyphs->items[glyphsNamedSofar]->name = glyphname; + } + glyphsNamedSofar++; + } + } + break; + } + case cff_CHARSET_FORMAT2: { + uint32_t glyphsNamedSofar = 1; + for (glyphid_t j = 0; j < charset->s; j++) { + glyphid_t first = charset->f2.range2[j].first; + for (glyphid_t k = 0; k <= charset->f2.range2[j].nleft; k++) { + cffsid_t sid = first + k; + sds glyphname = sdsget_cff_sid(sid, cffFile->string); + if (glyphsNamedSofar < glyphs->length && glyphname) { + glyphs->items[glyphsNamedSofar]->name = glyphname; + } + glyphsNamedSofar++; + } + } + break; + } + } + } +} + +static double qround(const double x) { + return otfcc_from_fixed(otfcc_to_fixed(x)); +} +static void applyCffMatrix(table_CFF *CFF_, table_glyf *glyf, const table_head *head) { + for (glyphid_t jj = 0; jj < glyf->length; jj++) { + glyf_Glyph *g = glyf->items[jj]; + table_CFF *fd = CFF_; + if (fd->fdArray && g->fdSelect.index < fd->fdArrayCount) { + fd = fd->fdArray[g->fdSelect.index]; + } + if (fd->fontMatrix) { + scale_t a = qround(head->unitsPerEm * fd->fontMatrix->a); + scale_t b = qround(head->unitsPerEm * fd->fontMatrix->b); + scale_t c = qround(head->unitsPerEm * fd->fontMatrix->c); + scale_t d = qround(head->unitsPerEm * fd->fontMatrix->d); + VQ x = iVQ.scale(fd->fontMatrix->x, head->unitsPerEm); + x.kernel = qround(x.kernel); + VQ y = iVQ.scale(fd->fontMatrix->y, head->unitsPerEm); + y.kernel = qround(y.kernel); + for (shapeid_t j = 0; j < g->contours.length; j++) { + glyf_Contour *contour = &g->contours.items[j]; + for (shapeid_t k = 0; k < contour->length; k++) { + VQ zx = iVQ.dup(contour->items[k].x); + VQ zy = iVQ.dup(contour->items[k].y); + iVQ.replace(&contour->items[k].x, iVQ.pointLinearTfm(x, a, zx, b, zy)); + iVQ.replace(&contour->items[k].y, iVQ.pointLinearTfm(y, c, zx, d, zy)); + iVQ.dispose(&zx), iVQ.dispose(&zy); + } + } + iVQ.dispose(&x), iVQ.dispose(&y); + } + } +} + +table_CFFAndGlyf otfcc_readCFFAndGlyfTables(const otfcc_Packet packet, const otfcc_Options *options, + const table_head *head) { + table_CFFAndGlyf ret; + ret.meta = NULL; + ret.glyphs = NULL; + + cff_extract_context context; + context.fdArrayIndex = -1; + context.meta = NULL; + context.glyphs = NULL; + context.cffFile = NULL; + FOR_TABLE(OTFCC_CHR('C','F','F',' '), table) { + font_file_pointer data = table.data; + uint32_t length = table.length; + cff_File *cffFile = cff_openStream(data, length, options); + context.cffFile = cffFile; + context.meta = table_iCFF.create(); + + // Extract data in TOP DICT + cff_iDict.parseToCallback(cffFile->top_dict.data, + cffFile->top_dict.offset[1] - cffFile->top_dict.offset[0], + &context, callback_extract_fd); + + if (!context.meta->fontName) { + context.meta->fontName = sdsget_cff_sid(391, cffFile->name); + } + + // We have FDArray + if (cffFile->font_dict.count) { + context.meta->fdArrayCount = cffFile->font_dict.count; + NEW(context.meta->fdArray, context.meta->fdArrayCount); + for (tableid_t j = 0; j < context.meta->fdArrayCount; j++) { + context.meta->fdArray[j] = table_iCFF.create(); + context.fdArrayIndex = j; + cff_iDict.parseToCallback( + cffFile->font_dict.data + cffFile->font_dict.offset[j] - 1, + cffFile->font_dict.offset[j + 1] - cffFile->font_dict.offset[j], &context, + callback_extract_fd); + if (!context.meta->fdArray[j]->fontName) { + context.meta->fdArray[j]->fontName = sdscatprintf(sdsempty(), "_Subfont%d", j); + } + } + } + ret.meta = context.meta; + + // Extract data of outlines + context.seed = 0x1234567887654321; + if (context.meta->privateDict) { + context.seed = + (uint64_t)context.meta->privateDict->initialRandomSeed ^ 0x1234567887654321; + } + table_glyf *glyphs = table_iGlyf.createN(cffFile->char_strings.count); + context.glyphs = glyphs; + for (glyphid_t j = 0; j < glyphs->length; j++) { + buildOutline(j, &context, options); + } + + applyCffMatrix(context.meta, context.glyphs, head); + + // Name glyphs according charset + nameGlyphsAccordingToCFF(&context); + + ret.glyphs = context.glyphs; + cff_close(cffFile); + } + return ret; +} + +static void pdDeltaToJson(json_value *target, const char *field, arity_t count, double *values) { + if (!count || !values) return; + json_value *a = json_array_new(count); + for (arity_t j = 0; j < count; j++) { + json_array_push(a, json_double_new(values[j])); + } + json_object_push(target, field, a); +} + +static json_value *pdToJson(const cff_PrivateDict *pd) { + json_value *_pd = json_object_new(24); + pdDeltaToJson(_pd, "blueValues", pd->blueValuesCount, pd->blueValues); + pdDeltaToJson(_pd, "otherBlues", pd->otherBluesCount, pd->otherBlues); + pdDeltaToJson(_pd, "familyBlues", pd->familyBluesCount, pd->familyBlues); + pdDeltaToJson(_pd, "familyOtherBlues", pd->familyOtherBluesCount, pd->familyOtherBlues); + pdDeltaToJson(_pd, "stemSnapH", pd->stemSnapHCount, pd->stemSnapH); + pdDeltaToJson(_pd, "stemSnapV", pd->stemSnapVCount, pd->stemSnapV); + if (pd->blueScale != DEFAULT_BLUE_SCALE) + json_object_push(_pd, "blueScale", json_double_new(pd->blueScale)); + if (pd->blueShift != DEFAULT_BLUE_SHIFT) + json_object_push(_pd, "blueShift", json_double_new(pd->blueShift)); + if (pd->blueFuzz != DEFAULT_BLUE_FUZZ) + json_object_push(_pd, "blueFuzz", json_double_new(pd->blueFuzz)); + if (pd->stdHW) json_object_push(_pd, "stdHW", json_double_new(pd->stdHW)); + if (pd->stdVW) json_object_push(_pd, "stdVW", json_double_new(pd->stdVW)); + if (pd->forceBold) json_object_push(_pd, "forceBold", json_boolean_new(pd->forceBold)); + if (pd->languageGroup) + json_object_push(_pd, "languageGroup", json_double_new(pd->languageGroup)); + if (pd->expansionFactor != DEFAULT_EXPANSION_FACTOR) + json_object_push(_pd, "expansionFactor", json_double_new(pd->expansionFactor)); + if (pd->initialRandomSeed) + json_object_push(_pd, "initialRandomSeed", json_double_new(pd->initialRandomSeed)); + if (pd->defaultWidthX) + json_object_push(_pd, "defaultWidthX", json_double_new(pd->defaultWidthX)); + if (pd->nominalWidthX) + json_object_push(_pd, "nominalWidthX", json_double_new(pd->nominalWidthX)); + return _pd; +} +static json_value *fdToJson(const table_CFF *table) { + json_value *_CFF_ = json_object_new(24); + + if (table->isCID) json_object_push(_CFF_, "isCID", json_boolean_new(table->isCID)); + + if (table->version) json_object_push(_CFF_, "version", json_from_sds(table->version)); + if (table->notice) json_object_push(_CFF_, "notice", json_from_sds(table->notice)); + if (table->copyright) json_object_push(_CFF_, "copyright", json_from_sds(table->copyright)); + if (table->fontName) json_object_push(_CFF_, "fontName", json_from_sds(table->fontName)); + if (table->fullName) json_object_push(_CFF_, "fullName", json_from_sds(table->fullName)); + if (table->familyName) json_object_push(_CFF_, "familyName", json_from_sds(table->familyName)); + if (table->weight) json_object_push(_CFF_, "weight", json_from_sds(table->weight)); + + if (table->isFixedPitch) + json_object_push(_CFF_, "isFixedPitch", json_boolean_new(table->isFixedPitch)); + if (table->italicAngle) + json_object_push(_CFF_, "italicAngle", json_double_new(table->italicAngle)); + if (table->underlinePosition != -100) + json_object_push(_CFF_, "underlinePosition", json_double_new(table->underlinePosition)); + if (table->underlineThickness != 50) + json_object_push(_CFF_, "underlineThickness", json_double_new(table->underlineThickness)); + if (table->strokeWidth) + json_object_push(_CFF_, "strokeWidth", json_double_new(table->strokeWidth)); + if (table->fontBBoxLeft) + json_object_push(_CFF_, "fontBBoxLeft", json_double_new(table->fontBBoxLeft)); + if (table->fontBBoxBottom) + json_object_push(_CFF_, "fontBBoxBottom", json_double_new(table->fontBBoxBottom)); + if (table->fontBBoxRight) + json_object_push(_CFF_, "fontBBoxRight", json_double_new(table->fontBBoxRight)); + if (table->fontBBoxTop) + json_object_push(_CFF_, "fontBBoxTop", json_double_new(table->fontBBoxTop)); + + if (table->fontMatrix) { + json_value *_fontMatrix = json_object_new(6); + json_object_push(_fontMatrix, "a", json_double_new(table->fontMatrix->a)); + json_object_push(_fontMatrix, "b", json_double_new(table->fontMatrix->b)); + json_object_push(_fontMatrix, "c", json_double_new(table->fontMatrix->c)); + json_object_push(_fontMatrix, "d", json_double_new(table->fontMatrix->d)); + json_object_push(_fontMatrix, "x", json_new_VQ(table->fontMatrix->x, NULL)); + json_object_push(_fontMatrix, "y", json_new_VQ(table->fontMatrix->y, NULL)); + json_object_push(_CFF_, "fontMatrix", _fontMatrix); + } + if (table->privateDict) { json_object_push(_CFF_, "privates", pdToJson(table->privateDict)); } + + if (table->cidRegistry && table->cidOrdering) { + json_object_push(_CFF_, "cidRegistry", json_from_sds(table->cidRegistry)); + json_object_push(_CFF_, "cidOrdering", json_from_sds(table->cidOrdering)); + json_object_push(_CFF_, "cidSupplement", json_integer_new(table->cidSupplement)); + } + if (table->fdArray) { + json_value *_fdArray = json_object_new(table->fdArrayCount); + for (tableid_t j = 0; j < table->fdArrayCount; j++) { + sds name = table->fdArray[j]->fontName; + table->fdArray[j]->fontName = NULL; + json_object_push(_fdArray, name, fdToJson(table->fdArray[j])); + table->fdArray[j]->fontName = name; + } + json_object_push(_CFF_, "fdArray", _fdArray); + } + return _CFF_; +} + +void otfcc_dumpCFF(const table_CFF *table, json_value *root, const otfcc_Options *options) { + if (!table) return; + loggedStep("CFF") { + json_object_push(root, "CFF_", fdToJson(table)); + } +} + +static void pdDeltaFromJson(json_value *dump, arity_t *count, double **array) { + if (!dump || dump->type != json_array) return; + *count = dump->u.array.length; + NEW(*array, *count); + for (arity_t j = 0; j < *count; j++) { + (*array)[j] = json_numof(dump->u.array.values[j]); + } +} +static cff_PrivateDict *pdFromJson(json_value *dump) { + if (!dump || dump->type != json_object) return NULL; + cff_PrivateDict *pd = otfcc_newCff_private(); + pdDeltaFromJson(json_obj_get(dump, "blueValues"), &(pd->blueValuesCount), &(pd->blueValues)); + pdDeltaFromJson(json_obj_get(dump, "otherBlues"), &(pd->otherBluesCount), &(pd->otherBlues)); + pdDeltaFromJson(json_obj_get(dump, "familyBlues"), &(pd->familyBluesCount), &(pd->familyBlues)); + pdDeltaFromJson(json_obj_get(dump, "familyOtherBlues"), &(pd->familyOtherBluesCount), + &(pd->familyOtherBlues)); + pdDeltaFromJson(json_obj_get(dump, "stemSnapH"), &(pd->stemSnapHCount), &(pd->stemSnapH)); + pdDeltaFromJson(json_obj_get(dump, "stemSnapV"), &(pd->stemSnapVCount), &(pd->stemSnapV)); + + pd->blueScale = json_obj_getnum_fallback(dump, "blueScale", DEFAULT_BLUE_SCALE); + pd->blueShift = json_obj_getnum_fallback(dump, "blueShift", DEFAULT_BLUE_SHIFT); + pd->blueFuzz = json_obj_getnum_fallback(dump, "blueFuzz", DEFAULT_BLUE_FUZZ); + pd->stdHW = json_obj_getnum(dump, "stdHW"); + pd->stdVW = json_obj_getnum(dump, "stdVW"); + pd->forceBold = json_obj_getbool(dump, "forceBold"); + pd->languageGroup = json_obj_getnum(dump, "languageGroup"); + pd->expansionFactor = + json_obj_getnum_fallback(dump, "expansionFactor", DEFAULT_EXPANSION_FACTOR); + pd->initialRandomSeed = json_obj_getnum(dump, "initialRandomSeed"); + // Not used -- they will be automatically calculated + // pd->defaultWidthX = json_obj_getnum(dump, "defaultWidthX"); + // pd->nominalWidthX = json_obj_getnum(dump, "nominalWidthX"); + + return pd; +} +static table_CFF *fdFromJson(const json_value *dump, const otfcc_Options *options, bool topLevel) { + table_CFF *table = table_iCFF.create(); + if (!dump || dump->type != json_object) return table; + // Names + table->version = json_obj_getsds(dump, "version"); + table->notice = json_obj_getsds(dump, "notice"); + table->copyright = json_obj_getsds(dump, "copyright"); + table->fontName = json_obj_getsds(dump, "fontName"); + table->fullName = json_obj_getsds(dump, "fullName"); + table->familyName = json_obj_getsds(dump, "familyName"); + table->weight = json_obj_getsds(dump, "weight"); + + // Metrics + table->isFixedPitch = json_obj_getbool(dump, "isFixedPitch"); + table->italicAngle = json_obj_getnum(dump, "italicAngle"); + table->underlinePosition = json_obj_getnum_fallback(dump, "underlinePosition", -100.0); + table->underlineThickness = json_obj_getnum_fallback(dump, "underlineThickness", 50.0); + table->strokeWidth = json_obj_getnum(dump, "strokeWidth"); + table->fontBBoxLeft = json_obj_getnum(dump, "fontBBoxLeft"); + table->fontBBoxBottom = json_obj_getnum(dump, "fontBBoxBottom"); + table->fontBBoxRight = json_obj_getnum(dump, "fontBBoxRight"); + table->fontBBoxTop = json_obj_getnum(dump, "fontBBoxTop"); + + // fontMatrix + // Need? + /* + json_value *fmatdump = json_obj_get_type(dump, "fontMatrix", json_object); + if (fmatdump) { + NEW(table->fontMatrix); + table->fontMatrix->a = json_obj_getnum(fmatdump, "a"); + table->fontMatrix->b = json_obj_getnum(fmatdump, "b"); + table->fontMatrix->c = json_obj_getnum(fmatdump, "c"); + table->fontMatrix->d = json_obj_getnum(fmatdump, "d"); + table->fontMatrix->x = json_obj_getnum(fmatdump, "x"); + table->fontMatrix->y = json_obj_getnum(fmatdump, "y"); + } + */ + + // privates + table->privateDict = pdFromJson(json_obj_get_type(dump, "privates", json_object)); + + // CID + table->cidRegistry = json_obj_getsds(dump, "cidRegistry"); + table->cidOrdering = json_obj_getsds(dump, "cidOrdering"); + table->cidSupplement = json_obj_getint(dump, "cidSupplement"); + table->UIDBase = json_obj_getint(dump, "UIDBase"); + table->cidCount = json_obj_getint(dump, "cidCount"); + table->cidFontVersion = json_obj_getnum(dump, "cidFontVersion"); + table->cidFontRevision = json_obj_getnum(dump, "cidFontRevision"); + + // fdArray + json_value *fdarraydump = json_obj_get_type(dump, "fdArray", json_object); + if (fdarraydump) { + table->isCID = true; + table->fdArrayCount = fdarraydump->u.object.length; + NEW(table->fdArray, table->fdArrayCount); + for (tableid_t j = 0; j < table->fdArrayCount; j++) { + table->fdArray[j] = fdFromJson(fdarraydump->u.object.values[j].value, options, false); + if (table->fdArray[j]->fontName) { sdsfree(table->fdArray[j]->fontName); } + table->fdArray[j]->fontName = sdsnewlen(fdarraydump->u.object.values[j].name, + fdarraydump->u.object.values[j].name_length); + } + } + if (!table->fontName) table->fontName = sdsnew("CARYLL_CFFFONT"); + if (!table->privateDict) table->privateDict = otfcc_newCff_private(); + // Deal with force-cid + if (topLevel && options->force_cid && !table->fdArray) { + table->fdArrayCount = 1; + NEW(table->fdArray, table->fdArrayCount); + table->fdArray[0] = table_iCFF.create(); + table_CFF *fd0 = table->fdArray[0]; + fd0->privateDict = table->privateDict; + table->privateDict = otfcc_newCff_private(); + fd0->fontName = sdscat(sdsdup(table->fontName), "-subfont0"); + table->isCID = true; + } + if (table->isCID && !table->cidRegistry) { table->cidRegistry = sdsnew("CARYLL"); } + if (table->isCID && !table->cidOrdering) { table->cidOrdering = sdsnew("OTFCCAUTOCID"); } + return table; +} +table_CFF *otfcc_parseCFF(const json_value *root, const otfcc_Options *options) { + json_value *dump = json_obj_get_type(root, "CFF_", json_object); + if (!dump) { + return NULL; + } else { + table_CFF *cff = NULL; + loggedStep("CFF") { + cff = fdFromJson(dump, options, true); + } + return cff; + } +} + +typedef struct { + table_glyf *glyf; + uint16_t defaultWidth; + uint16_t nominalWidthX; + const otfcc_Options *options; + cff_SubrGraph graph; +} cff_charstring_builder_context; +typedef struct { + caryll_Buffer *charStrings; + caryll_Buffer *subroutines; +} cff_charStringAndSubrs; + +static void cff_make_charstrings(cff_charstring_builder_context *context, caryll_Buffer **s, + caryll_Buffer **gs, caryll_Buffer **ls) { + if (context->glyf->length == 0) { return; } + for (glyphid_t j = 0; j < context->glyf->length; j++) { + cff_CharstringIL *il = cff_compileGlyphToIL(context->glyf->items[j], context->defaultWidth, + context->nominalWidthX); + cff_optimizeIL(il, context->options); + cff_insertILToGraph(&context->graph, il); + FREE(il->instr); + FREE(il); + } + cff_ilGraphToBuffers(&context->graph, s, gs, ls, context->options); +} + +// String table management +typedef struct { + int sid; + char *str; + UT_hash_handle hh; +} cff_sid_entry; + +static int sidof(cff_sid_entry **h, sds s) { + cff_sid_entry *item = NULL; + HASH_FIND_STR(*h, s, item); + if (item) { + return 391 + item->sid; + } else { + NEW(item); + item->sid = HASH_COUNT(*h); + item->str = sdsdup(s); + HASH_ADD_STR(*h, str, item); + return 391 + item->sid; + } +} + +static cff_DictEntry *cffdict_givemeablank(cff_Dict *dict) { + dict->count++; + RESIZE(dict->ents, dict->count); + return &(dict->ents[dict->count - 1]); +} +static void cffdict_input(cff_Dict *dict, uint32_t op, cff_Value_Type t, arity_t arity, ...) { + cff_DictEntry *last = cffdict_givemeablank(dict); + last->op = op; + last->cnt = arity; + NEW(last->vals, arity); + + va_list ap; + va_start(ap, arity); + for (arity_t j = 0; j < arity; j++) { + if (t == cff_DOUBLE) { + double x = va_arg(ap, double); + if (x == round(x)) { + last->vals[j].t = cff_INTEGER; + last->vals[j].i = round(x); + } else { + last->vals[j].t = cff_DOUBLE; + last->vals[j].d = x; + } + } else { + int x = va_arg(ap, int); + last->vals[j].t = t; + last->vals[j].i = x; + } + } + va_end(ap); +} +static void cffdict_input_array(cff_Dict *dict, uint32_t op, cff_Value_Type t, arity_t arity, + double *arr) { + if (!arity || !arr) return; + cff_DictEntry *last = cffdict_givemeablank(dict); + last->op = op; + last->cnt = arity; + NEW(last->vals, arity); + for (arity_t j = 0; j < arity; j++) { + double x = arr[j]; + if (t == cff_DOUBLE) { + if (x == round(x)) { + last->vals[j].t = cff_INTEGER; + last->vals[j].i = round(x); + } else { + last->vals[j].t = cff_DOUBLE; + last->vals[j].d = x; + } + } else { + last->vals[j].t = t; + last->vals[j].i = round(x); + } + } +} + +static cff_Dict *cff_make_fd_dict(table_CFF *fd, cff_sid_entry **h) { + cff_Dict *dict = cff_iDict.create(); + // ROS + if (fd->cidRegistry && fd->cidOrdering) { + cffdict_input(dict, op_ROS, cff_INTEGER, 3, sidof(h, fd->cidRegistry), + sidof(h, fd->cidOrdering), fd->cidSupplement); + } + + // CFF Names + if (fd->version) cffdict_input(dict, op_version, cff_INTEGER, 1, sidof(h, fd->version)); + if (fd->notice) cffdict_input(dict, op_Notice, cff_INTEGER, 1, sidof(h, fd->notice)); + if (fd->copyright) cffdict_input(dict, op_Copyright, cff_INTEGER, 1, sidof(h, fd->copyright)); + if (fd->fullName) cffdict_input(dict, op_FullName, cff_INTEGER, 1, sidof(h, fd->fullName)); + if (fd->familyName) + cffdict_input(dict, op_FamilyName, cff_INTEGER, 1, sidof(h, fd->familyName)); + if (fd->weight) cffdict_input(dict, op_Weight, cff_INTEGER, 1, sidof(h, fd->weight)); + + // CFF Metrics + cffdict_input(dict, op_FontBBox, cff_DOUBLE, 4, fd->fontBBoxLeft, fd->fontBBoxBottom, + fd->fontBBoxRight, fd->fontBBoxTop); + cffdict_input(dict, op_isFixedPitch, cff_INTEGER, 1, (int)fd->isFixedPitch); + cffdict_input(dict, op_ItalicAngle, cff_DOUBLE, 1, fd->italicAngle); + cffdict_input(dict, op_UnderlinePosition, cff_DOUBLE, 1, fd->underlinePosition); + cffdict_input(dict, op_UnderlineThickness, cff_DOUBLE, 1, fd->underlineThickness); + cffdict_input(dict, op_StrokeWidth, cff_DOUBLE, 1, fd->strokeWidth); + if (fd->fontMatrix) { + cffdict_input(dict, op_FontMatrix, cff_DOUBLE, 6, + fd->fontMatrix->a, fd->fontMatrix->b, + fd->fontMatrix->c, fd->fontMatrix->d, + iVQ.getStill(fd->fontMatrix->x), iVQ.getStill(fd->fontMatrix->y)); + } + + // CID specific + if (fd->fontName) cffdict_input(dict, op_FontName, cff_INTEGER, 1, sidof(h, fd->fontName)); + if (fd->cidFontVersion) + cffdict_input(dict, op_CIDFontVersion, cff_DOUBLE, 1, fd->cidFontVersion); + if (fd->cidFontRevision) + cffdict_input(dict, op_CIDFontRevision, cff_DOUBLE, 1, fd->cidFontRevision); + if (fd->cidCount) cffdict_input(dict, op_CIDCount, cff_INTEGER, 1, fd->cidCount); + if (fd->UIDBase) cffdict_input(dict, op_UIDBase, cff_INTEGER, 1, fd->UIDBase); + return dict; +} + +static cff_Dict *cff_make_private_dict(cff_PrivateDict *pd) { + cff_Dict *dict; + NEW(dict); + if (!pd) return dict; + // DELTA arrays + cffdict_input_array(dict, op_BlueValues, cff_DOUBLE, pd->blueValuesCount, pd->blueValues); + cffdict_input_array(dict, op_OtherBlues, cff_DOUBLE, pd->otherBluesCount, pd->otherBlues); + cffdict_input_array(dict, op_FamilyBlues, cff_DOUBLE, pd->familyBluesCount, pd->familyBlues); + cffdict_input_array(dict, op_FamilyOtherBlues, cff_DOUBLE, pd->familyOtherBluesCount, + pd->familyOtherBlues); + cffdict_input_array(dict, op_StemSnapH, cff_DOUBLE, pd->stemSnapHCount, pd->stemSnapH); + cffdict_input_array(dict, op_StemSnapV, cff_DOUBLE, pd->stemSnapVCount, pd->stemSnapV); + + // Private scalars + cffdict_input(dict, op_BlueScale, cff_DOUBLE, 1, pd->blueScale); + cffdict_input(dict, op_BlueShift, cff_DOUBLE, 1, pd->blueShift); + cffdict_input(dict, op_BlueFuzz, cff_DOUBLE, 1, pd->blueFuzz); + cffdict_input(dict, op_StdHW, cff_DOUBLE, 1, pd->stdHW); + cffdict_input(dict, op_StdVW, cff_DOUBLE, 1, pd->stdVW); + cffdict_input(dict, op_ForceBold, cff_INTEGER, 1, (int)pd->forceBold); + cffdict_input(dict, op_LanguageGroup, cff_INTEGER, 1, pd->languageGroup); + cffdict_input(dict, op_ExpansionFactor, cff_DOUBLE, 1, pd->expansionFactor); + cffdict_input(dict, op_initialRandomSeed, cff_DOUBLE, 1, pd->initialRandomSeed); + + // op_nominalWidthX are currently not used + cffdict_input(dict, op_defaultWidthX, cff_DOUBLE, 1, pd->defaultWidthX); + cffdict_input(dict, op_nominalWidthX, cff_DOUBLE, 1, pd->nominalWidthX); + + return dict; +} + +static int by_sid(cff_sid_entry *a, cff_sid_entry *b) { + return a->sid - b->sid; +} +static caryll_Buffer *callback_makestringindex(void *context, uint32_t i) { + caryll_Buffer **blobs = context; + return blobs[i]; +} +static caryll_Buffer *cffstrings_to_indexblob(cff_sid_entry **h) { + HASH_SORT(*h, by_sid); + caryll_Buffer **blobs; + uint32_t n = HASH_COUNT(*h); + NEW(blobs, n); + uint32_t j = 0; + cff_sid_entry *item, *tmp; + HASH_ITER(hh, *h, item, tmp) { + blobs[j] = bufnew(); + bufwrite_sds(blobs[j], item->str); + HASH_DEL(*h, item); + sdsfree(item->str); + FREE(item); + j++; + } + + cff_Index *strings = cff_iIndex.fromCallback(blobs, n, callback_makestringindex); + FREE(blobs); + caryll_Buffer *final_blob = cff_iIndex.build(strings); + cff_iIndex.free(strings); + final_blob->cursor = final_blob->size; + return final_blob; +} + +static caryll_Buffer *cff_compile_nameindex(table_CFF *cff) { + cff_Index *nameIndex = cff_iIndex.create(); + nameIndex->count = 1; + nameIndex->offSize = 4; + NEW(nameIndex->offset, 2); + if (!cff->fontName) { cff->fontName = sdsnew("Caryll-CFF-FONT"); } + nameIndex->offset[0] = 1; + nameIndex->offset[1] = (uint32_t)(sdslen(cff->fontName) + 1); + NEW(nameIndex->data, 1 + sdslen(cff->fontName)); + memcpy(nameIndex->data, cff->fontName, sdslen(cff->fontName)); + // CFF Name INDEX + caryll_Buffer *buf = cff_iIndex.build(nameIndex); + cff_iIndex.free(nameIndex); + if (cff->fontName) { + sdsfree(cff->fontName); + cff->fontName = NULL; + } + return buf; +} + +static caryll_Buffer *cff_make_charset(table_CFF *cff, table_glyf *glyf, + cff_sid_entry **stringHash) { + cff_Charset *charset; + NEW(charset); + if (glyf->length > 1) { // At least two glyphs + charset->t = cff_CHARSET_FORMAT2; + charset->s = 1; // One segment only + charset->f2.format = 2; + NEW(charset->f2.range2); + if (cff->isCID) { + charset->f2.range2[0].first = 1; + charset->f2.range2[0].nleft = glyf->length - 2; + } else { + for (glyphid_t j = 1; j < glyf->length; j++) { + sidof(stringHash, glyf->items[j]->name); + } + charset->f2.range2[0].first = sidof(stringHash, glyf->items[1]->name); + charset->f2.range2[0].nleft = glyf->length - 2; + } + } else { + charset->t = cff_CHARSET_ISOADOBE; + } + caryll_Buffer *c = cff_build_Charset(*charset); + if (charset->t == cff_CHARSET_FORMAT2) { FREE(charset->f2.range2); } + FREE(charset); + return c; +} + +static caryll_Buffer *cff_make_fdselect(table_CFF *cff, table_glyf *glyf) { + if (!cff->isCID) return bufnew(); + // We choose format 3 here + uint32_t ranges = 1; + uint8_t current = 0; + cff_FDSelect *fds; + NEW(fds); + fds->t = cff_FDSELECT_UNSPECED; + if (!glyf->length) goto done; + uint8_t fdi0 = glyf->items[0]->fdSelect.index; + if (fdi0 > cff->fdArrayCount) fdi0 = 0; + current = fdi0; + for (glyphid_t j = 1; j < glyf->length; j++) { + uint8_t fdi = glyf->items[j]->fdSelect.index; + if (fdi > cff->fdArrayCount) fdi = 0; + if (fdi != current) { + current = fdi; + ranges++; + } + } + NEW(fds->f3.range3, ranges); + fds->f3.range3[0].first = 0; + fds->f3.range3[0].fd = current = fdi0; + for (glyphid_t j = 1; j < glyf->length; j++) { + uint8_t fdi = glyf->items[j]->fdSelect.index; + if (fdi > cff->fdArrayCount) fdi = 0; + if (glyf->items[j]->fdSelect.index != current) { + current = fdi; + fds->s++; + fds->f3.range3[fds->s].first = j; + fds->f3.range3[fds->s].fd = current; + } + } + fds->t = cff_FDSELECT_FORMAT3; + fds->s = ranges; + fds->f3.format = 3; + fds->f3.nranges = ranges; + fds->f3.sentinel = glyf->length; +done:; + caryll_Buffer *e = cff_build_FDSelect(*fds); + cff_close_FDSelect(*fds); + FREE(fds); + return e; +} + +typedef struct { + table_CFF **fdArray; + cff_sid_entry **stringHash; +} fdarray_compile_context; +static caryll_Buffer *callback_makefd(void *_context, uint32_t i) { + fdarray_compile_context *context = (fdarray_compile_context *)_context; + cff_Dict *fd = cff_make_fd_dict(context->fdArray[i], context->stringHash); + caryll_Buffer *blob = cff_iDict.build(fd); + bufwrite_bufdel(blob, cff_buildOffset(0xEEEEEEEE)); + bufwrite_bufdel(blob, cff_buildOffset(0xFFFFFFFF)); + bufwrite_bufdel(blob, cff_encodeCffOperator(op_Private)); + cff_iDict.build(fd); + return blob; +} +static cff_Index *cff_make_fdarray(tableid_t fdArrayCount, table_CFF **fdArray, + cff_sid_entry **stringHash) { + fdarray_compile_context context; + context.fdArray = fdArray; + context.stringHash = stringHash; + + return cff_iIndex.fromCallback(&context, fdArrayCount, callback_makefd); +} + +static caryll_Buffer *writecff_CIDKeyed(table_CFF *cff, table_glyf *glyf, + const otfcc_Options *options) { + caryll_Buffer *blob = bufnew(); + // The Strings hashtable + cff_sid_entry *stringHash = NULL; + + // CFF Header + caryll_Buffer *h = cff_buildHeader(); + caryll_Buffer *n = cff_compile_nameindex(cff); + + // cff top DICT + cff_Dict *top = cff_make_fd_dict(cff, &stringHash); + caryll_Buffer *t = cff_iDict.build(top); + cff_iDict.free(top); + + // cff top PRIVATE + cff_Dict *top_pd = cff_make_private_dict(cff->privateDict); + caryll_Buffer *p = cff_iDict.build(top_pd); + bufwrite_bufdel(p, cff_buildOffset(0xFFFFFFFF)); + bufwrite_bufdel(p, cff_encodeCffOperator(op_Subrs)); + cff_iDict.free(top_pd); + + // FDSelect + caryll_Buffer *e = cff_make_fdselect(cff, glyf); + + // FDArray + cff_Index *fdArrayIndex = NULL; + caryll_Buffer *r; + if (cff->isCID) { + fdArrayIndex = cff_make_fdarray(cff->fdArrayCount, cff->fdArray, &stringHash); + r = cff_iIndex.build(fdArrayIndex); + } else { + NEW(r); + } + + // cff charset : Allocate string index terms for glyph names + caryll_Buffer *c = cff_make_charset(cff, glyf, &stringHash); + + // CFF String Index + caryll_Buffer *i = cffstrings_to_indexblob(&stringHash); + + // CFF Charstrings + caryll_Buffer *s; + caryll_Buffer *gs; + caryll_Buffer *ls; + { + cff_charstring_builder_context g2cContext; + g2cContext.glyf = glyf; + g2cContext.defaultWidth = cff->privateDict->defaultWidthX; + g2cContext.nominalWidthX = cff->privateDict->nominalWidthX; + g2cContext.options = options; + cff_iSubrGraph.init(&g2cContext.graph); + g2cContext.graph.doSubroutinize = options->cff_doSubroutinize; + + cff_make_charstrings(&g2cContext, &s, &gs, &ls); + + cff_iSubrGraph.dispose(&g2cContext.graph); + } + + // Merge these data + uint32_t additionalTopDictOpsSize = 0; + uint32_t off = (uint32_t)(h->size + n->size + 11 + t->size); + if (c->size != 0) additionalTopDictOpsSize += 6; // op_charset + if (e->size != 0) additionalTopDictOpsSize += 7; // op_FDSelect + if (s->size != 0) additionalTopDictOpsSize += 6; // op_CharStrings + if (p->size != 0) additionalTopDictOpsSize += 11; // op_Private + if (r->size != 0) additionalTopDictOpsSize += 7; // op_FDArray + + // Start building CFF table + bufwrite_bufdel(blob, h); // header + bufwrite_bufdel(blob, n); // name index + int32_t delta_size = (uint32_t)(t->size + additionalTopDictOpsSize + 1); + bufwrite_bufdel(blob, bufninit(11, 0, 1, // top dict index headerone item + 4, // four bytes per offset + 0, 0, 0, 1, // offset 1 + (delta_size >> 24) & 0xff, (delta_size >> 16) & 0xff, + (delta_size >> 8) & 0xff, + delta_size & 0xff) // offset 2 + ); + + bufwrite_bufdel(blob, t); // top dict body + + // top dict offsets + off += additionalTopDictOpsSize + i->size + gs->size; + if (c->size != 0) { + bufwrite_bufdel(blob, cff_buildOffset(off)); + bufwrite_bufdel(blob, cff_encodeCffOperator(op_charset)); + off += c->size; + } + if (e->size != 0) { + bufwrite_bufdel(blob, cff_buildOffset(off)); + bufwrite_bufdel(blob, cff_encodeCffOperator(op_FDSelect)); + off += e->size; + } + if (s->size != 0) { + bufwrite_bufdel(blob, cff_buildOffset(off)); + bufwrite_bufdel(blob, cff_encodeCffOperator(op_CharStrings)); + off += s->size; + } + if (p->size != 0) { + bufwrite_bufdel(blob, cff_buildOffset((uint32_t)(p->size))); + bufwrite_bufdel(blob, cff_buildOffset(off)); + bufwrite_bufdel(blob, cff_encodeCffOperator(op_Private)); + off += p->size; + } + if (r->size != 0) { + bufwrite_bufdel(blob, cff_buildOffset(off)); + bufwrite_bufdel(blob, cff_encodeCffOperator(op_FDArray)); + off += r->size; + } + + bufwrite_bufdel(blob, i); // string index + bufwrite_bufdel(blob, gs); // gsubr + bufwrite_bufdel(blob, c); // charset + bufwrite_bufdel(blob, e); // fdselect + bufwrite_bufdel(blob, s); // charstring + size_t *startingPositionOfPrivates; + NEW(startingPositionOfPrivates, 1 + cff->fdArrayCount); + startingPositionOfPrivates[0] = blob->cursor; + bufwrite_bufdel(blob, p); // top private + size_t *endingPositionOfPrivates; + NEW(endingPositionOfPrivates, 1 + cff->fdArrayCount); + endingPositionOfPrivates[0] = blob->cursor; + if (cff->isCID) { // fdarray and fdarray privates + uint32_t fdArrayPrivatesStartOffset = off; + caryll_Buffer **fdArrayPrivates; + NEW(fdArrayPrivates, cff->fdArrayCount); + for (tableid_t j = 0; j < cff->fdArrayCount; j++) { + cff_Dict *pd = cff_make_private_dict(cff->fdArray[j]->privateDict); + caryll_Buffer *p = cff_iDict.build(pd); + bufwrite_bufdel(p, cff_buildOffset(0xFFFFFFFF)); + bufwrite_bufdel(p, cff_encodeCffOperator(op_Subrs)); + cff_iDict.free(pd); + fdArrayPrivates[j] = p; + uint8_t *privateLengthPtr = &(fdArrayIndex->data[fdArrayIndex->offset[j + 1] - 11]); + privateLengthPtr[0] = (p->size >> 24) & 0xFF; + privateLengthPtr[1] = (p->size >> 16) & 0xFF; + privateLengthPtr[2] = (p->size >> 8) & 0xFF; + privateLengthPtr[3] = (p->size >> 0) & 0xFF; + uint8_t *privateOffsetPtr = &(fdArrayIndex->data[fdArrayIndex->offset[j + 1] - 6]); + privateOffsetPtr[0] = (fdArrayPrivatesStartOffset >> 24) & 0xFF; + privateOffsetPtr[1] = (fdArrayPrivatesStartOffset >> 16) & 0xFF; + privateOffsetPtr[2] = (fdArrayPrivatesStartOffset >> 8) & 0xFF; + privateOffsetPtr[3] = (fdArrayPrivatesStartOffset >> 0) & 0xFF; + fdArrayPrivatesStartOffset += p->size; + } + buffree(r); // fdarray + r = cff_iIndex.build(fdArrayIndex); + cff_iIndex.free(fdArrayIndex); + bufwrite_bufdel(blob, r); + for (tableid_t j = 0; j < cff->fdArrayCount; j++) { + startingPositionOfPrivates[j + 1] = blob->cursor; + bufwrite_bufdel(blob, fdArrayPrivates[j]); + endingPositionOfPrivates[j + 1] = blob->cursor; + } + FREE(fdArrayPrivates); + } else { + bufwrite_bufdel(blob, r); + } + size_t positionOfLocalSubroutines = blob->cursor; + bufwrite_bufdel(blob, ls); + for (tableid_t j = 0; j < cff->fdArrayCount + 1; j++) { + size_t lsOffset = positionOfLocalSubroutines - startingPositionOfPrivates[j]; + uint8_t *ptr = &(blob->data[endingPositionOfPrivates[j] - 5]); + ptr[0] = (lsOffset >> 24) & 0xFF; + ptr[1] = (lsOffset >> 16) & 0xFF; + ptr[2] = (lsOffset >> 8) & 0xFF; + ptr[3] = (lsOffset >> 0) & 0xFF; + } + FREE(startingPositionOfPrivates); + FREE(endingPositionOfPrivates); + // finish + return blob; +} + +caryll_Buffer *otfcc_buildCFF(const table_CFFAndGlyf cffAndGlyf, const otfcc_Options *options) { + return writecff_CIDKeyed(cffAndGlyf.meta, cffAndGlyf.glyphs, options); +} |