From 06f38edcc743dfd9c6ba73d1d30cc815682fcb01 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Sat, 12 Apr 2025 16:25:34 -0400 Subject: [PATCH] Add pdfioFileCreateFontObjFromData function (Issue #120) --- CHANGES.md | 3 +- pdfio-content.c | 787 +++++++++++++++++++++++++++--------------------- pdfio-content.h | 1 + testttf.c | 93 +++++- ttf.c | 568 ++++++++++++++++++++++------------ ttf.h | 3 +- 6 files changed, 914 insertions(+), 541 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d9e8303..ab4e1b2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,8 @@ Changes in PDFio v1.6.0 - YYYY-MM-DD ------------------- - +- Added `pdfioFileCreateFontObjFromData` function for embedding fonts in + memory (Issue #120) v1.5.2 - 2025-04-12 diff --git a/pdfio-content.c b/pdfio-content.c index 29566f9..ff64aba 100644 --- a/pdfio-content.c +++ b/pdfio-content.c @@ -84,6 +84,7 @@ typedef pdfio_obj_t *(*_pdfio_image_func_t)(pdfio_dict_t *dict, int fd); static pdfio_obj_t *copy_jpeg(pdfio_dict_t *dict, int fd); static pdfio_obj_t *copy_png(pdfio_dict_t *dict, int fd); static bool create_cp1252(pdfio_file_t *pdf); +static pdfio_obj_t *create_font(pdfio_obj_t *file_obj, ttf_t *font, bool unicode); static pdfio_obj_t *create_image(pdfio_dict_t *dict, const unsigned char *data, size_t width, size_t height, size_t depth, size_t num_colors, bool alpha); #ifdef HAVE_LIBPNG static void png_error_func(png_structp pp, png_const_charp message); @@ -1536,9 +1537,84 @@ pdfioFileCreateFontObjFromBase( // -// 'pdfioFileCreateFontObjFromFile()' - Add a font object to a PDF file. +// 'pdfioFileCreateFontObjFromData()' - Add a font in memory to a PDF file. // -// This function embeds a TrueType/OpenType font into a PDF file. The +// This function embeds TrueType/OpenType font data into a PDF file. The +// "unicode" parameter controls whether the font is encoded for two-byte +// characters (potentially full Unicode, but more typically a subset) +// or to only support the Windows CP1252 (ISO-8859-1 with additional +// characters such as the Euro symbol) subset of Unicode. +// + +pdfio_obj_t * // O - Font object +pdfioFileCreateFontObjFromData( + pdfio_file_t *pdf, // I - PDF file + const void *data, // I - Font data in memory + size_t datasize, // I - Size of font in memory + bool unicode) // I - Force Unicode +{ + ttf_t *font; // TrueType font + pdfio_obj_t *obj, // Font object + *file_obj = NULL; // File object + pdfio_dict_t *file; // Font file dictionary + pdfio_stream_t *st = NULL; // Font stream + + + // Range check input... + if (!pdf) + return (NULL); + + if (!data || !datasize) + { + _pdfioFileError(pdf, "No TrueType/OpenType data specified."); + return (NULL); + } + + // Create a TrueType font object from the data... + if ((font = ttfCreateData(data, datasize, 0, (ttf_err_cb_t)ttf_error_cb, pdf)) == NULL) + return (NULL); + + // Create the font file dictionary and object... + if ((file = pdfioDictCreate(pdf)) == NULL) + goto error; + + pdfioDictSetName(file, "Filter", "FlateDecode"); + + if ((file_obj = pdfioFileCreateObj(pdf, file)) == NULL) + goto error; + + if ((st = pdfioObjCreateStream(file_obj, PDFIO_FILTER_FLATE)) == NULL) + goto error; + + if (!pdfioStreamWrite(st, data, datasize)) + goto error; + + pdfioStreamClose(st); + + // Create the font object... + if ((obj = create_font(file_obj, font, unicode)) == NULL) + ttfDelete(font); + + return (obj); + + // If we get here we had an unrecoverable error... + error: + + if (st) + pdfioStreamClose(st); + else + pdfioObjClose(file_obj); + + ttfDelete(font); + + return (NULL); +} + + +// +// 'pdfioFileCreateFontObjFromFile()' - Add a font file to a PDF file. +// +// This function embeds a TrueType/OpenType font file into a PDF file. The // "unicode" parameter controls whether the font is encoded for two-byte // characters (potentially full Unicode, but more typically a subset) // or to only support the Windows CP1252 (ISO-8859-1 with additional @@ -1552,16 +1628,10 @@ pdfioFileCreateFontObjFromFile( bool unicode) // I - Force Unicode { ttf_t *font; // TrueType font - ttf_rect_t bounds; // Font bounds - pdfio_dict_t *dict, // Font dictionary - *desc, // Font descriptor - *file; // Font file dictionary - pdfio_obj_t *obj = NULL, // Font object - *desc_obj, // Font descriptor object - *file_obj; // Font file object - const char *basefont; // Base font name - pdfio_array_t *bbox; // Font bounding box array - pdfio_stream_t *st; // Font stream + pdfio_dict_t *file; // Font file dictionary + pdfio_obj_t *obj, // Font object + *file_obj = NULL; // Font file object + pdfio_stream_t *st = NULL; // Font stream int fd = -1; // File unsigned char buffer[16384]; // Read buffer ssize_t bytes; // Bytes read @@ -1584,360 +1654,50 @@ pdfioFileCreateFontObjFromFile( } if ((font = ttfCreate(filename, 0, (ttf_err_cb_t)ttf_error_cb, pdf)) == NULL) - { - close(fd); - return (NULL); - } + goto error; // Create the font file dictionary and object... if ((file = pdfioDictCreate(pdf)) == NULL) - goto done; + goto error; pdfioDictSetName(file, "Filter", "FlateDecode"); if ((file_obj = pdfioFileCreateObj(pdf, file)) == NULL) - goto done; + goto error; if ((st = pdfioObjCreateStream(file_obj, PDFIO_FILTER_FLATE)) == NULL) - goto done; + goto error; while ((bytes = read(fd, buffer, sizeof(buffer))) > 0) { if (!pdfioStreamWrite(st, buffer, (size_t)bytes)) - { - pdfioStreamClose(st); - goto done; - } + goto error; } close(fd); - fd = -1; + pdfioStreamClose(st); - // Create the font descriptor dictionary and object... - if ((bbox = pdfioArrayCreate(pdf)) == NULL) - goto done; + // Create the font object... + if ((obj = create_font(file_obj, font, unicode)) == NULL) + ttfDelete(font); - ttfGetBounds(font, &bounds); + return (obj); - pdfioArrayAppendNumber(bbox, bounds.left); - pdfioArrayAppendNumber(bbox, bounds.bottom); - pdfioArrayAppendNumber(bbox, bounds.right); - pdfioArrayAppendNumber(bbox, bounds.top); - - if ((desc = pdfioDictCreate(pdf)) == NULL) - goto done; - - basefont = pdfioStringCreate(pdf, ttfGetPostScriptName(font)); - - pdfioDictSetName(desc, "Type", "FontDescriptor"); - pdfioDictSetName(desc, "FontName", basefont); - pdfioDictSetObj(desc, "FontFile2", file_obj); - pdfioDictSetNumber(desc, "Flags", ttfIsFixedPitch(font) ? 0x21 : 0x20); - pdfioDictSetArray(desc, "FontBBox", bbox); - pdfioDictSetNumber(desc, "ItalicAngle", ttfGetItalicAngle(font)); - pdfioDictSetNumber(desc, "Ascent", ttfGetAscent(font)); - pdfioDictSetNumber(desc, "Descent", ttfGetDescent(font)); - pdfioDictSetNumber(desc, "CapHeight", ttfGetCapHeight(font)); - pdfioDictSetNumber(desc, "XHeight", ttfGetXHeight(font)); - // Note: No TrueType value exists for this but PDF requires it, so we - // calculate a generic value from 50 to 250 based on the weight... - pdfioDictSetNumber(desc, "StemV", ttfGetWeight(font) / 4 + 25); - - if ((desc_obj = pdfioFileCreateObj(pdf, desc)) == NULL) - goto done; - - pdfioObjClose(desc_obj); - - if (unicode) - { - // Unicode (CID) font... - pdfio_dict_t *cid2gid, // CIDToGIDMap dictionary - *to_unicode; // ToUnicode dictionary - pdfio_obj_t *cid2gid_obj, // CIDToGIDMap object - *to_unicode_obj;// ToUnicode object - size_t i, j, // Looping vars - num_cmap; // Number of CMap entries - const int *cmap; // CMap entries - int min_glyph, // First glyph - max_glyph; // Last glyph - unsigned short glyphs[65536]; // Glyph to Unicode mapping - unsigned char *bufptr, // Pointer into buffer - *bufend; // End of buffer - pdfio_dict_t *type2; // CIDFontType2 font dictionary - pdfio_obj_t *type2_obj; // CIDFontType2 font object - pdfio_array_t *descendants; // Decendant font list - pdfio_dict_t *sidict; // CIDSystemInfo dictionary - pdfio_array_t *w_array, // Width array - *temp_array; // Temporary width sub-array - int w0, w1; // Widths - - // Create a CIDSystemInfo mapping to Adobe UCS2 v0 (Unicode) - if ((sidict = pdfioDictCreate(pdf)) == NULL) - goto done; - - pdfioDictSetString(sidict, "Registry", "Adobe"); - pdfioDictSetString(sidict, "Ordering", "Identity"); - pdfioDictSetNumber(sidict, "Supplement", 0); - - // Create a CIDToGIDMap object for the Unicode font... - if ((cid2gid = pdfioDictCreate(pdf)) == NULL) - goto done; - -#ifndef DEBUG - pdfioDictSetName(cid2gid, "Filter", "FlateDecode"); -#endif // !DEBUG - - if ((cid2gid_obj = pdfioFileCreateObj(pdf, cid2gid)) == NULL) - goto done; - -#ifdef DEBUG - if ((st = pdfioObjCreateStream(cid2gid_obj, PDFIO_FILTER_NONE)) == NULL) -#else - if ((st = pdfioObjCreateStream(cid2gid_obj, PDFIO_FILTER_FLATE)) == NULL) -#endif // DEBUG - goto done; - - cmap = ttfGetCMap(font, &num_cmap); - min_glyph = 65536; - max_glyph = 0; - memset(glyphs, 0, sizeof(glyphs)); - - PDFIO_DEBUG("pdfioFileCreateFontObjFromFile: num_cmap=%u\n", (unsigned)num_cmap); - - for (i = 0, bufptr = buffer, bufend = buffer + sizeof(buffer); i < num_cmap; i ++) - { - PDFIO_DEBUG("pdfioFileCreateFontObjFromFile: cmap[%u]=%d\n", (unsigned)i, cmap[i]); - if (cmap[i] < 0 || cmap[i] >= (int)(sizeof(glyphs) / sizeof(glyphs[0]))) - { - // Map undefined glyph to .notdef... - *bufptr++ = 0; - *bufptr++ = 0; - } - else - { - // Map to specified glyph... - *bufptr++ = (unsigned char)(cmap[i] >> 8); - *bufptr++ = (unsigned char)(cmap[i] & 255); - - glyphs[cmap[i]] = (unsigned short)i; - if (cmap[i] < min_glyph) - min_glyph = cmap[i]; - if (cmap[i] > max_glyph) - max_glyph = cmap[i]; - } - - if (bufptr >= bufend) - { - // Flush buffer... - if (!pdfioStreamWrite(st, buffer, (size_t)(bufptr - buffer))) - { - pdfioStreamClose(st); - goto done; - } - - bufptr = buffer; - } - } - - if (bufptr > buffer) - { - // Flush buffer... - if (!pdfioStreamWrite(st, buffer, (size_t)(bufptr - buffer))) - { - pdfioStreamClose(st); - goto done; - } - } - - pdfioStreamClose(st); - - // ToUnicode mapping object - to_unicode = pdfioDictCreate(pdf); - pdfioDictSetName(to_unicode, "Type", "CMap"); - pdfioDictSetName(to_unicode, "CMapName", "Adobe-Identity-UCS2"); - pdfioDictSetDict(to_unicode, "CIDSystemInfo", sidict); - -#ifndef DEBUG - pdfioDictSetName(to_unicode, "Filter", "FlateDecode"); -#endif // !DEBUG - - if ((to_unicode_obj = pdfioFileCreateObj(pdf, to_unicode)) == NULL) - goto done; - -#ifdef DEBUG - if ((st = pdfioObjCreateStream(to_unicode_obj, PDFIO_FILTER_NONE)) == NULL) -#else - if ((st = pdfioObjCreateStream(to_unicode_obj, PDFIO_FILTER_FLATE)) == NULL) -#endif // DEBUG - goto done; - - pdfioStreamPuts(st, - "stream\n" - "/CIDInit /ProcSet findresource begin\n" - "12 dict begin\n" - "begincmap\n" - "/CIDSystemInfo<<\n" - "/Registry (Adobe)\n" - "/Ordering (UCS2)\n" - "/Supplement 0\n" - ">> def\n" - "/CMapName /Adobe-Identity-UCS2 def\n" - "/CMapType 2 def\n" - "1 begincodespacerange\n" - "<0000> \n" - "endcodespacerange\n" - "endcmap\n" - "CMapName currentdict /CMap defineresource pop\n" - "end\n" - "end\n"); - - pdfioStreamClose(st); - - // Create a CIDFontType2 dictionary for the Unicode font... - if ((type2 = pdfioDictCreate(pdf)) == NULL) - goto done; - - // Width array - if ((w_array = pdfioArrayCreate(pdf)) == NULL) - goto done; - - for (i = 0, w0 = ttfGetWidth(font, 0), w1 = 0; i < 65536; w0 = w1) - { - for (j = 1; (i + j) < 65536; j ++) - { - if ((w1 = ttfGetWidth(font, (int)(i + j))) != w0) - break; - } - - if (j >= 4) - { - // Encode a long sequence of zeros... - // Encode a repeating sequence... - pdfioArrayAppendNumber(w_array, (double)i); - pdfioArrayAppendNumber(w_array, (double)(i + j - 1)); - pdfioArrayAppendNumber(w_array, w0); - - i += j; - } - else - { - // Encode a non-repeating sequence... - pdfioArrayAppendNumber(w_array, (double)i); - - if ((temp_array = pdfioArrayCreate(pdf)) == NULL) - goto done; - - pdfioArrayAppendNumber(temp_array, w0); - for (i ++; i < 65536 && pdfioArrayGetSize(temp_array) < 8191; i ++, w0 = w1) - { - if ((w1 = ttfGetWidth(font, (int)i)) == w0 && i < 65530) - { - for (j = 1; j < 4; j ++) - { - if (ttfGetWidth(font, (int)(i + j)) != w0) - break; - } - - if (j >= 4) - break; - } - - pdfioArrayAppendNumber(temp_array, w1); - } - - pdfioArrayAppendArray(w_array, temp_array); - } - } - - // Then the dictionary for the CID base font... - pdfioDictSetName(type2, "Type", "Font"); - pdfioDictSetName(type2, "Subtype", "CIDFontType2"); - pdfioDictSetName(type2, "BaseFont", basefont); - pdfioDictSetDict(type2, "CIDSystemInfo", sidict); - pdfioDictSetObj(type2, "CIDToGIDMap", cid2gid_obj); - pdfioDictSetObj(type2, "FontDescriptor", desc_obj); - pdfioDictSetArray(type2, "W", w_array); - - if ((type2_obj = pdfioFileCreateObj(pdf, type2)) == NULL) - goto done; - - pdfioObjClose(type2_obj); - - // Create a Type 0 font object... - if ((descendants = pdfioArrayCreate(pdf)) == NULL) - goto done; - - pdfioArrayAppendObj(descendants, type2_obj); - - if ((dict = pdfioDictCreate(pdf)) == NULL) - goto done; - - pdfioDictSetName(dict, "Type", "Font"); - pdfioDictSetName(dict, "Subtype", "Type0"); - pdfioDictSetName(dict, "BaseFont", basefont); - pdfioDictSetArray(dict, "DescendantFonts", descendants); - pdfioDictSetName(dict, "Encoding", "Identity-H"); - pdfioDictSetObj(dict, "ToUnicode", to_unicode_obj); - - if ((obj = pdfioFileCreateObj(pdf, dict)) != NULL) - pdfioObjClose(obj); - } - else - { - // Simple (CP1282 or custom encoding) 8-bit font... - int ch; // Character - pdfio_array_t *w_array; // Widths array - - if (ttfGetMaxChar(font) >= 255 && !pdf->cp1252_obj && !create_cp1252(pdf)) - goto done; - - // Create a TrueType font object... - if ((dict = pdfioDictCreate(pdf)) == NULL) - goto done; - - pdfioDictSetName(dict, "Type", "Font"); - pdfioDictSetName(dict, "Subtype", "TrueType"); - pdfioDictSetName(dict, "BaseFont", basefont); - pdfioDictSetNumber(dict, "FirstChar", 32); - if (ttfGetMaxChar(font) >= 255) - { - pdfioDictSetObj(dict, "Encoding", pdf->cp1252_obj); - pdfioDictSetNumber(dict, "LastChar", 255); - } - else - { - pdfioDictSetNumber(dict, "LastChar", ttfGetMaxChar(font)); - } - - // Build a Widths array for CP1252/WinAnsiEncoding - if ((w_array = pdfioArrayCreate(pdf)) == NULL) - goto done; - - for (ch = 32; ch < 256 && ch < ttfGetMaxChar(font); ch ++) - { - if (ch >= 0x80 && ch < 0xa0) - pdfioArrayAppendNumber(w_array, ttfGetWidth(font, _pdfio_cp1252[ch - 0x80])); - else - pdfioArrayAppendNumber(w_array, ttfGetWidth(font, ch)); - } - - pdfioDictSetArray(dict, "Widths", w_array); - - pdfioDictSetObj(dict, "FontDescriptor", desc_obj); - - if ((obj = pdfioFileCreateObj(pdf, dict)) != NULL) - pdfioObjClose(obj); - } - - done: + // If we get here we had an unrecoverable error... + error: if (fd >= 0) close(fd); - _pdfioObjSetExtension(obj, font, (_pdfio_extfree_t)ttfDelete); + if (st) + pdfioStreamClose(st); + else + pdfioObjClose(file_obj); - return (obj); + ttfDelete(font); + + return (NULL); } @@ -3307,6 +3067,351 @@ create_cp1252(pdfio_file_t *pdf) // I - PDF file } +// +// 'create_font()' - Create the object for a TrueType font. +// + +static pdfio_obj_t * // O - Font object +create_font(pdfio_obj_t *file_obj, // I - Font file object + ttf_t *font, // I - TrueType font + bool unicode) // I - Force Unicode +{ + ttf_rect_t bounds; // Font bounds + pdfio_dict_t *dict, // Font dictionary + *desc; // Font descriptor + pdfio_obj_t *obj = NULL, // Font object + *desc_obj; // Font descriptor object + const char *basefont; // Base font name + pdfio_array_t *bbox; // Font bounding box array + pdfio_stream_t *st; // Font stream + + + // Create the font descriptor dictionary and object... + if ((bbox = pdfioArrayCreate(file_obj->pdf)) == NULL) + goto done; + + ttfGetBounds(font, &bounds); + + pdfioArrayAppendNumber(bbox, bounds.left); + pdfioArrayAppendNumber(bbox, bounds.bottom); + pdfioArrayAppendNumber(bbox, bounds.right); + pdfioArrayAppendNumber(bbox, bounds.top); + + if ((desc = pdfioDictCreate(file_obj->pdf)) == NULL) + goto done; + + if ((basefont = pdfioStringCreate(file_obj->pdf, ttfGetPostScriptName(font))) == NULL) + goto done; + + pdfioDictSetName(desc, "Type", "FontDescriptor"); + pdfioDictSetName(desc, "FontName", basefont); + pdfioDictSetObj(desc, "FontFile2", file_obj); + pdfioDictSetNumber(desc, "Flags", ttfIsFixedPitch(font) ? 0x21 : 0x20); + pdfioDictSetArray(desc, "FontBBox", bbox); + pdfioDictSetNumber(desc, "ItalicAngle", ttfGetItalicAngle(font)); + pdfioDictSetNumber(desc, "Ascent", ttfGetAscent(font)); + pdfioDictSetNumber(desc, "Descent", ttfGetDescent(font)); + pdfioDictSetNumber(desc, "CapHeight", ttfGetCapHeight(font)); + pdfioDictSetNumber(desc, "XHeight", ttfGetXHeight(font)); + // Note: No TrueType value exists for this but PDF requires it, so we + // calculate a generic value from 50 to 250 based on the weight... + pdfioDictSetNumber(desc, "StemV", ttfGetWeight(font) / 4 + 25); + + if ((desc_obj = pdfioFileCreateObj(file_obj->pdf, desc)) == NULL) + goto done; + + pdfioObjClose(desc_obj); + + if (unicode) + { + // Unicode (CID) font... + pdfio_dict_t *cid2gid, // CIDToGIDMap dictionary + *to_unicode; // ToUnicode dictionary + pdfio_obj_t *cid2gid_obj, // CIDToGIDMap object + *to_unicode_obj;// ToUnicode object + size_t i, j, // Looping vars + num_cmap; // Number of CMap entries + const int *cmap; // CMap entries + int min_glyph, // First glyph + max_glyph; // Last glyph + unsigned short glyphs[65536]; // Glyph to Unicode mapping + unsigned char buffer[16384], // Read buffer + *bufptr, // Pointer into buffer + *bufend; // End of buffer + pdfio_dict_t *type2; // CIDFontType2 font dictionary + pdfio_obj_t *type2_obj; // CIDFontType2 font object + pdfio_array_t *descendants; // Decendant font list + pdfio_dict_t *sidict; // CIDSystemInfo dictionary + pdfio_array_t *w_array, // Width array + *temp_array; // Temporary width sub-array + int w0, w1; // Widths + + // Create a CIDSystemInfo mapping to Adobe UCS2 v0 (Unicode) + if ((sidict = pdfioDictCreate(file_obj->pdf)) == NULL) + goto done; + + pdfioDictSetString(sidict, "Registry", "Adobe"); + pdfioDictSetString(sidict, "Ordering", "Identity"); + pdfioDictSetNumber(sidict, "Supplement", 0); + + // Create a CIDToGIDMap object for the Unicode font... + if ((cid2gid = pdfioDictCreate(file_obj->pdf)) == NULL) + goto done; + +#ifndef DEBUG + pdfioDictSetName(cid2gid, "Filter", "FlateDecode"); +#endif // !DEBUG + + if ((cid2gid_obj = pdfioFileCreateObj(file_obj->pdf, cid2gid)) == NULL) + goto done; + +#ifdef DEBUG + if ((st = pdfioObjCreateStream(cid2gid_obj, PDFIO_FILTER_NONE)) == NULL) +#else + if ((st = pdfioObjCreateStream(cid2gid_obj, PDFIO_FILTER_FLATE)) == NULL) +#endif // DEBUG + goto done; + + cmap = ttfGetCMap(font, &num_cmap); + min_glyph = 65536; + max_glyph = 0; + memset(glyphs, 0, sizeof(glyphs)); + + PDFIO_DEBUG("create_font: num_cmap=%u\n", (unsigned)num_cmap); + + for (i = 0, bufptr = buffer, bufend = buffer + sizeof(buffer); i < num_cmap; i ++) + { + PDFIO_DEBUG("create_font: cmap[%u]=%d\n", (unsigned)i, cmap[i]); + if (cmap[i] < 0 || cmap[i] >= (int)(sizeof(glyphs) / sizeof(glyphs[0]))) + { + // Map undefined glyph to .notdef... + *bufptr++ = 0; + *bufptr++ = 0; + } + else + { + // Map to specified glyph... + *bufptr++ = (unsigned char)(cmap[i] >> 8); + *bufptr++ = (unsigned char)(cmap[i] & 255); + + glyphs[cmap[i]] = (unsigned short)i; + if (cmap[i] < min_glyph) + min_glyph = cmap[i]; + if (cmap[i] > max_glyph) + max_glyph = cmap[i]; + } + + if (bufptr >= bufend) + { + // Flush buffer... + if (!pdfioStreamWrite(st, buffer, (size_t)(bufptr - buffer))) + { + pdfioStreamClose(st); + goto done; + } + + bufptr = buffer; + } + } + + if (bufptr > buffer) + { + // Flush buffer... + if (!pdfioStreamWrite(st, buffer, (size_t)(bufptr - buffer))) + { + pdfioStreamClose(st); + goto done; + } + } + + pdfioStreamClose(st); + + // ToUnicode mapping object + to_unicode = pdfioDictCreate(file_obj->pdf); + pdfioDictSetName(to_unicode, "Type", "CMap"); + pdfioDictSetName(to_unicode, "CMapName", "Adobe-Identity-UCS2"); + pdfioDictSetDict(to_unicode, "CIDSystemInfo", sidict); + +#ifndef DEBUG + pdfioDictSetName(to_unicode, "Filter", "FlateDecode"); +#endif // !DEBUG + + if ((to_unicode_obj = pdfioFileCreateObj(file_obj->pdf, to_unicode)) == NULL) + goto done; + +#ifdef DEBUG + if ((st = pdfioObjCreateStream(to_unicode_obj, PDFIO_FILTER_NONE)) == NULL) +#else + if ((st = pdfioObjCreateStream(to_unicode_obj, PDFIO_FILTER_FLATE)) == NULL) +#endif // DEBUG + goto done; + + pdfioStreamPuts(st, + "stream\n" + "/CIDInit /ProcSet findresource begin\n" + "12 dict begin\n" + "begincmap\n" + "/CIDSystemInfo<<\n" + "/Registry (Adobe)\n" + "/Ordering (UCS2)\n" + "/Supplement 0\n" + ">> def\n" + "/CMapName /Adobe-Identity-UCS2 def\n" + "/CMapType 2 def\n" + "1 begincodespacerange\n" + "<0000> \n" + "endcodespacerange\n" + "endcmap\n" + "CMapName currentdict /CMap defineresource pop\n" + "end\n" + "end\n"); + + pdfioStreamClose(st); + + // Create a CIDFontType2 dictionary for the Unicode font... + if ((type2 = pdfioDictCreate(file_obj->pdf)) == NULL) + goto done; + + // Width array + if ((w_array = pdfioArrayCreate(file_obj->pdf)) == NULL) + goto done; + + for (i = 0, w0 = ttfGetWidth(font, 0), w1 = 0; i < 65536; w0 = w1) + { + for (j = 1; (i + j) < 65536; j ++) + { + if ((w1 = ttfGetWidth(font, (int)(i + j))) != w0) + break; + } + + if (j >= 4) + { + // Encode a long sequence of zeros... + // Encode a repeating sequence... + pdfioArrayAppendNumber(w_array, (double)i); + pdfioArrayAppendNumber(w_array, (double)(i + j - 1)); + pdfioArrayAppendNumber(w_array, w0); + + i += j; + } + else + { + // Encode a non-repeating sequence... + pdfioArrayAppendNumber(w_array, (double)i); + + if ((temp_array = pdfioArrayCreate(file_obj->pdf)) == NULL) + goto done; + + pdfioArrayAppendNumber(temp_array, w0); + for (i ++; i < 65536 && pdfioArrayGetSize(temp_array) < 8191; i ++, w0 = w1) + { + if ((w1 = ttfGetWidth(font, (int)i)) == w0 && i < 65530) + { + for (j = 1; j < 4; j ++) + { + if (ttfGetWidth(font, (int)(i + j)) != w0) + break; + } + + if (j >= 4) + break; + } + + pdfioArrayAppendNumber(temp_array, w1); + } + + pdfioArrayAppendArray(w_array, temp_array); + } + } + + // Then the dictionary for the CID base font... + pdfioDictSetName(type2, "Type", "Font"); + pdfioDictSetName(type2, "Subtype", "CIDFontType2"); + pdfioDictSetName(type2, "BaseFont", basefont); + pdfioDictSetDict(type2, "CIDSystemInfo", sidict); + pdfioDictSetObj(type2, "CIDToGIDMap", cid2gid_obj); + pdfioDictSetObj(type2, "FontDescriptor", desc_obj); + pdfioDictSetArray(type2, "W", w_array); + + if ((type2_obj = pdfioFileCreateObj(file_obj->pdf, type2)) == NULL) + goto done; + + pdfioObjClose(type2_obj); + + // Create a Type 0 font object... + if ((descendants = pdfioArrayCreate(file_obj->pdf)) == NULL) + goto done; + + pdfioArrayAppendObj(descendants, type2_obj); + + if ((dict = pdfioDictCreate(file_obj->pdf)) == NULL) + goto done; + + pdfioDictSetName(dict, "Type", "Font"); + pdfioDictSetName(dict, "Subtype", "Type0"); + pdfioDictSetName(dict, "BaseFont", basefont); + pdfioDictSetArray(dict, "DescendantFonts", descendants); + pdfioDictSetName(dict, "Encoding", "Identity-H"); + pdfioDictSetObj(dict, "ToUnicode", to_unicode_obj); + + if ((obj = pdfioFileCreateObj(file_obj->pdf, dict)) != NULL) + pdfioObjClose(obj); + } + else + { + // Simple (CP1282 or custom encoding) 8-bit font... + int ch; // Character + pdfio_array_t *w_array; // Widths array + + if (ttfGetMaxChar(font) >= 255 && !file_obj->pdf->cp1252_obj && !create_cp1252(file_obj->pdf)) + goto done; + + // Create a TrueType font object... + if ((dict = pdfioDictCreate(file_obj->pdf)) == NULL) + goto done; + + pdfioDictSetName(dict, "Type", "Font"); + pdfioDictSetName(dict, "Subtype", "TrueType"); + pdfioDictSetName(dict, "BaseFont", basefont); + pdfioDictSetNumber(dict, "FirstChar", 32); + if (ttfGetMaxChar(font) >= 255) + { + pdfioDictSetObj(dict, "Encoding", file_obj->pdf->cp1252_obj); + pdfioDictSetNumber(dict, "LastChar", 255); + } + else + { + pdfioDictSetNumber(dict, "LastChar", ttfGetMaxChar(font)); + } + + // Build a Widths array for CP1252/WinAnsiEncoding + if ((w_array = pdfioArrayCreate(file_obj->pdf)) == NULL) + goto done; + + for (ch = 32; ch < 256 && ch < ttfGetMaxChar(font); ch ++) + { + if (ch >= 0x80 && ch < 0xa0) + pdfioArrayAppendNumber(w_array, ttfGetWidth(font, _pdfio_cp1252[ch - 0x80])); + else + pdfioArrayAppendNumber(w_array, ttfGetWidth(font, ch)); + } + + pdfioDictSetArray(dict, "Widths", w_array); + + pdfioDictSetObj(dict, "FontDescriptor", desc_obj); + + if ((obj = pdfioFileCreateObj(file_obj->pdf, dict)) != NULL) + pdfioObjClose(obj); + } + + done: + + _pdfioObjSetExtension(obj, font, (_pdfio_extfree_t)ttfDelete); + + return (obj); +} + + // // 'create_image()' - Create an image object from some data. // diff --git a/pdfio-content.h b/pdfio-content.h index dbbcc75..ca29ab3 100644 --- a/pdfio-content.h +++ b/pdfio-content.h @@ -128,6 +128,7 @@ extern bool pdfioContentTextShowJustified(pdfio_stream_t *st, bool unicode, siz // Resource helpers... extern pdfio_obj_t *pdfioFileCreateFontObjFromBase(pdfio_file_t *pdf, const char *name) _PDFIO_PUBLIC; +extern pdfio_obj_t *pdfioFileCreateFontObjFromData(pdfio_file_t *pdf, const void *data, size_t datasize, bool unicode) _PDFIO_PUBLIC; extern pdfio_obj_t *pdfioFileCreateFontObjFromFile(pdfio_file_t *pdf, const char *filename, bool unicode) _PDFIO_PUBLIC; extern pdfio_obj_t *pdfioFileCreateICCObjFromData(pdfio_file_t *pdf, const unsigned char *data, size_t datalen, size_t num_colors) _PDFIO_PUBLIC; extern pdfio_obj_t *pdfioFileCreateICCObjFromFile(pdfio_file_t *pdf, const char *filename, size_t num_colors) _PDFIO_PUBLIC; diff --git a/testttf.c b/testttf.c index 0bf1fc3..db4455f 100644 --- a/testttf.c +++ b/testttf.c @@ -3,7 +3,7 @@ // // https://github.com/michaelrsweet/ttf // -// Copyright © 2018-2024 by Michael R Sweet. +// Copyright © 2018-2025 by Michael R Sweet. // // Licensed under Apache License v2.0. See the file "LICENSE" for more // information. @@ -14,6 +14,10 @@ // #include +#include +#include +#include +#include #include "ttf.h" @@ -81,9 +85,13 @@ test_font(const char *filename) // I - Font filename int i, // Looping var errors = 0; // Number of errors ttf_t *font; // Font + struct stat fileinfo; // Font file information + FILE *fp = NULL; // File pointer + void *data = NULL; // Memory buffer for font file const char *value; // Font (string) value int intvalue; // Font (integer) value float realvalue; // Font (real) value + char psname[1024]; // Postscript font name ttf_rect_t bounds; // Bounds ttf_rect_t extents; // Extents size_t num_fonts; // Number of fonts @@ -220,6 +228,9 @@ test_font(const char *filename) // I - Font filename if ((value = ttfGetPostScriptName(font)) != NULL) { printf("PASS (%s)\n", value); + + strncpy(psname, value, sizeof(psname) - 1); + psname[sizeof(psname) - 1] = '\0'; } else { @@ -300,6 +311,86 @@ test_font(const char *filename) // I - Font filename puts("PASS (false)"); ttfDelete(font); + font = NULL; + + // Now copy the font to memory and open it that way... + printf("fopen(\"%s\", \"rb\"): ", filename); + if ((fp = fopen(filename, "rb")) == NULL) + { + printf("FAIL (%s)\n", strerror(errno)); + errors ++; + } + else + { + printf("PASS (%d)\n", fileno(fp)); + printf("fstat(%d): ", fileno(fp)); + if (fstat(fileno(fp), &fileinfo)) + { + printf("FAIL (%s)\n", strerror(errno)); + errors ++; + } + else + { + printf("PASS (%lu bytes)\n", (unsigned long)fileinfo.st_size); + + fputs("malloc(): ", stdout); + if ((data = malloc((size_t)fileinfo.st_size)) == NULL) + { + printf("FAIL (%s)\n", strerror(errno)); + errors ++; + } + else + { + puts("PASS"); + fputs("fread(): ", stdout); + if (fread(data, (size_t)fileinfo.st_size, 1, fp) != 1) + { + printf("FAIL (%s)\n", strerror(errno)); + errors ++; + } + else + { + puts("PASS"); + fputs("ttfCreateData(): ", stdout); + if ((font = ttfCreateData(data, (size_t)fileinfo.st_size, /*idx*/0, error_cb, /*err_data*/NULL)) == NULL) + { + puts("FAIL"); + errors ++; + } + else + { + puts("PASS"); + + fputs("ttfGetPostScriptName: ", stdout); + if ((value = ttfGetPostScriptName(font)) != NULL) + { + if (!strcmp(value, psname)) + { + printf("PASS (%s)\n", value); + } + else + { + printf("FAIL (got \"%s\", expected \"%s\")\n", value, psname); + errors ++; + } + } + else + { + puts("FAIL"); + errors ++; + } + } + } + } + } + + if (fp) + fclose(fp); + + free(data); + + ttfDelete(font); + } return (errors); } diff --git a/ttf.c b/ttf.c index 8cabc7e..6f3d6ef 100644 --- a/ttf.c +++ b/ttf.c @@ -177,9 +177,19 @@ typedef struct _ttf_off_names_s // OFF/TTF naming table unsigned storage_size; // Size of storage area } _ttf_off_names_t; +typedef size_t (*_ttf_read_cb_t)(ttf_t *font, void *buffer, size_t bytes); + // Font read callback, returns number of bytes read +typedef bool (*_ttf_seek_cb_t)(ttf_t *font, size_t offset); + // Font seek callback, returns `true` on success + struct _ttf_s { - int fd; // File descriptor + _ttf_read_cb_t read_cb; // Read callback + _ttf_seek_cb_t seek_cb; // Seek callback + int file_fd; // File descriptor for ttfCreate + const char *data; // Font data for ttfCreateData + size_t data_size; // Size of font data for ttfCreateData + size_t data_offset; // Offset within input size_t idx; // Font number in file ttf_err_cb_t err_cb; // Error callback, if any void *err_data; // Error callback data @@ -245,10 +255,10 @@ typedef struct _ttf_off_head_s // Font header } _ttf_off_head_t; #define TTF_OFF_macStyle_Bold 0x01 -#define TTF_OFF_macStyle_Italic 0x02 +#define TTF_OFF_macStyle_Italic 0x02 #define TTF_OFF_macStyle_Underline 0x04 #define TTF_OFF_macStyle_Outline 0x08 -#define TTF_OFF_macStyle_Shadow 0x10 +#define TTF_OFF_macStyle_Shadow 0x10 #define TTF_OFF_macStyle_Condensed 0x20 #define TTF_OFF_macStyle_Extended 0x40 @@ -282,7 +292,12 @@ typedef struct _ttf_off_post_s // PostScript information // static char *copy_name(ttf_t *font, unsigned name_id); +static ttf_t *create_font(const char *filename, const void *data, size_t datasize, size_t idx, ttf_err_cb_t err_cb, void *err_data); static void errorf(ttf_t *font, const char *message, ...) TTF_FORMAT_ARGS(2,3); +static size_t fd_read_cb(ttf_t *font, void *buffer, size_t bytes); +static bool fd_seek_cb(ttf_t *font, size_t offset); +static size_t mem_read_cb(ttf_t *font, void *buffer, size_t bytes); +static bool mem_seek_cb(ttf_t *font, size_t offset); static bool read_cmap(ttf_t *font); static bool read_head(ttf_t *font, _ttf_off_head_t *head); static bool read_hhea(ttf_t *font, _ttf_off_hhea_t *hhea); @@ -329,15 +344,6 @@ ttfCreate(const char *filename, // I - Filename ttf_err_cb_t err_cb, // I - Error callback or `NULL` to log to stderr void *err_data) // I - Error callback data { - ttf_t *font = NULL; // New font object - size_t i; // Looping var - _ttf_metric_t *widths = NULL; // Glyph metrics - _ttf_off_head_t head; // head table - _ttf_off_hhea_t hhea; // hhea table - _ttf_off_os_2_t os_2; // OS/2 table - _ttf_off_post_t post; // PostScript table - - TTF_DEBUG("ttfCreate(filename=\"%s\", idx=%u, err_cb=%p, err_data=%p)\n", filename, (unsigned)idx, err_cb, err_data); // Range check input.. @@ -347,178 +353,57 @@ ttfCreate(const char *filename, // I - Filename return (NULL); } - // Allocate memory... - if ((font = (ttf_t *)calloc(1, sizeof(ttf_t))) == NULL) + // Open and return the font... + return (create_font(filename, /*data*/NULL, /*datasize*/0, idx, err_cb, err_data)); +} + + +// +// 'ttfCreateData()' - Create a new font object from a memory buffer. +// +// This function creates a new font object from a memory buffer. The "data" +// argument specifies a pointer to the first byte of data and the "datasize" +// argument specifies the length of the memory buffer in bytes. +// +// > Note: The caller is responsible for ensuring that the memory buffer is +// > available until the font object is deleted with @link ttfDelete@. +// +// The "idx" argument specifies the font to load from a collection - the first +// font is number `0`. Once created, you can call the @link ttfGetNumFonts@ +// function to determine whether the loaded font file is a collection with more +// than one font. +// +// The "err_cb" and "err_data" arguments specify a callback function and data +// pointer for receiving error messages. If `NULL`, errors are sent to the +// `stderr` file. The callback function receives the data pointer and a text +// message string, for example: +// +// ``` +// void my_err_cb(void *err_data, const char *message) +// { +// fprintf(stderr, "ERROR: %s\n", message); +// } +// ``` +// + +ttf_t * // O - New font object +ttfCreateData(const void *data, // I - Buffer + size_t datasize, // I - Size of buffer in bytes + size_t idx, // I - Font number to create in collection (0-based) + ttf_err_cb_t err_cb, // I - Error callback or `NULL` to log to stderr + void *err_data) // I - Error callback data +{ + TTF_DEBUG("ttfCreateData(data=%p, datasize=%lu, idx=%u, err_cb=%p, err_data=%p)\n", data, (unsigned long)datasize, (unsigned)idx, err_cb, err_data); + + // Range check input.. + if (!data || datasize == 0) + { + errno = EINVAL; return (NULL); - - font->idx = idx; - font->err_cb = err_cb; - font->err_data = err_data; - - // Open the font file... - if ((font->fd = open(filename, O_RDONLY | O_BINARY)) < 0) - { - errorf(font, "Unable to open '%s': %s", filename, strerror(errno)); - goto error; } - TTF_DEBUG("ttfCreate: fd=%d\n", font->fd); - - // Read the table of contents and the identifying names... - if (!read_table(font)) - goto error; - - TTF_DEBUG("ttfCreate: num_entries=%d\n", font->table.num_entries); - - if (!read_names(font)) - goto error; - - TTF_DEBUG("ttfCreate: num_names=%d\n", font->names.num_names); - - // Copy key font meta data strings... - font->copyright = copy_name(font, TTF_OFF_Copyright); - font->family = copy_name(font, TTF_OFF_FontFamily); - font->postscript_name = copy_name(font, TTF_OFF_PostScriptName); - font->version = copy_name(font, TTF_OFF_FontVersion); - - if (read_post(font, &post)) - { - font->italic_angle = post.italicAngle; - font->is_fixed = post.isFixedPitch != 0; - } - - TTF_DEBUG("ttfCreate: copyright=\"%s\"\n", font->copyright); - TTF_DEBUG("ttfCreate: family=\"%s\"\n", font->family); - TTF_DEBUG("ttfCreate: postscript_name=\"%s\"\n", font->postscript_name); - TTF_DEBUG("ttfCreate: version=\"%s\"\n", font->version); - TTF_DEBUG("ttfCreate: italic_angle=%g\n", font->italic_angle); - TTF_DEBUG("ttfCreate: is_fixed=%s\n", font->is_fixed ? "true" : "false"); - - if (!read_cmap(font)) - goto error; - - if (!read_head(font, &head)) - goto error; - - font->units = (float)head.unitsPerEm; - font->x_max = head.xMax; - font->x_min = head.xMin; - font->y_max = head.yMax; - font->y_min = head.yMin; - - if (head.macStyle & TTF_OFF_macStyle_Italic) - { - if (font->postscript_name && strstr(font->postscript_name, "Oblique")) - font->style = TTF_STYLE_OBLIQUE; - else - font->style = TTF_STYLE_ITALIC; - } - else - font->style = TTF_STYLE_NORMAL; - - if (!read_hhea(font, &hhea)) - goto error; - - font->ascent = hhea.ascender; - font->descent = hhea.descender; - - if (read_maxp(font) < 0) - goto error; - - if (hhea.numberOfHMetrics > 0) - { - if ((widths = read_hmtx(font, &hhea)) == NULL) - goto error; - } - else - { - errorf(font, "Number of horizontal metrics is 0."); - goto error; - } - - if (read_os_2(font, &os_2)) - { - // Copy key values from OS/2 table... - static const ttf_stretch_t stretches[] = - { - TTF_STRETCH_ULTRA_CONDENSED, // ultra-condensed - TTF_STRETCH_EXTRA_CONDENSED, // extra-condensed - TTF_STRETCH_CONDENSED, // condensed - TTF_STRETCH_SEMI_CONDENSED, // semi-condensed - TTF_STRETCH_NORMAL, // normal - TTF_STRETCH_SEMI_EXPANDED, // semi-expanded - TTF_STRETCH_EXPANDED, // expanded - TTF_STRETCH_EXTRA_EXPANDED, // extra-expanded - TTF_STRETCH_ULTRA_EXPANDED // ultra-expanded - }; - - if (os_2.usWidthClass >= 1 && os_2.usWidthClass <= (int)(sizeof(stretches) / sizeof(stretches[0]))) - font->stretch = stretches[os_2.usWidthClass - 1]; - - font->weight = (short)os_2.usWeightClass; - font->cap_height = os_2.sCapHeight; - font->x_height = os_2.sxHeight; - } - else - { - // Default key values since there isn't an OS/2 table... - TTF_DEBUG("ttfCreate: Unable to read OS/2 table.\n"); - - font->weight = 400; - } - - if (font->cap_height == 0) - font->cap_height = font->ascent; - - if (font->x_height == 0) - font->x_height = 3 * font->ascent / 5; - - // Build a sparse glyph widths table... - font->min_char = -1; - - for (i = 0; i < font->num_cmap; i ++) - { - if (font->cmap[i] >= 0) - { - int bin = (int)i / 256, // Sub-array bin - glyph = font->cmap[i]; // Glyph index - - // Update min/max... - if (font->min_char < 0) - font->min_char = (int)i; - - font->max_char = (int)i; - - // Allocate a sub-array as needed... - if (!font->widths[bin]) - font->widths[bin] = (_ttf_metric_t *)calloc(256, sizeof(_ttf_metric_t)); - - // Copy the width of the specified glyph or the last one if we are past - // the end of the table... - if (glyph >= hhea.numberOfHMetrics) - font->widths[bin][i & 255] = widths[hhea.numberOfHMetrics - 1]; - else - font->widths[bin][i & 255] = widths[glyph]; - } - -#ifdef DEBUG - if (i >= ' ' && i < 127 && font->widths[0]) - TTF_DEBUG("ttfCreate: width['%c']=%d(%d)\n", (char)i, font->widths[0][i].width, font->widths[0][i].left_bearing); -#endif // DEBUG - } - - // Cleanup and return the font... - free(widths); - - return (font); - - // If we get here something bad happened... - error: - - free(widths); - ttfDelete(font); - - return (NULL); + // Open and return the font... + return (create_font(/*filename*/NULL, data, datasize, idx, err_cb, err_data)); } @@ -537,8 +422,8 @@ ttfDelete(ttf_t *font) // I - Font return; // Close the font file... - if (font->fd >= 0) - close(font->fd); + if (font->file_fd >= 0) + close(font->file_fd); // Free all memory used... free(font->copyright); @@ -1043,6 +928,219 @@ copy_name(ttf_t *font, // I - Font } +// +// 'create_font()' - Create a font object from the file or data. +// + +static ttf_t * +create_font(const char *filename, // I - Filename of `NULL` + const void *data, // I - Data pointer or `NULL` + size_t datasize, // I - Size of data or 0 + size_t idx, // I - Font index + ttf_err_cb_t err_cb, // I - Error callback function + void *err_data) // I - Error callback data +{ + ttf_t *font = NULL; // New font object + size_t i; // Looping var + _ttf_metric_t *widths = NULL; // Glyph metrics + _ttf_off_head_t head; // head table + _ttf_off_hhea_t hhea; // hhea table + _ttf_off_os_2_t os_2; // OS/2 table + _ttf_off_post_t post; // PostScript table + + + TTF_DEBUG("create_font(filename=\"%s\", data=%p, datasize=%lu, idx=%u, err_cb=%p, err_data=%p)\n", filename, data, (unsigned long)datasize, (unsigned)idx, err_cb, err_data); + + // Allocate memory... + if ((font = (ttf_t *)calloc(1, sizeof(ttf_t))) == NULL) + return (NULL); + + font->idx = idx; + font->err_cb = err_cb; + font->err_data = err_data; + + if (filename) + { + // Open the font file... + if ((font->file_fd = open(filename, O_RDONLY | O_BINARY)) < 0) + { + errorf(font, "Unable to open '%s': %s", filename, strerror(errno)); + goto error; + } + + TTF_DEBUG("create_font: file_fd=%d\n", font->file_fd); + + font->read_cb = fd_read_cb; + font->seek_cb = fd_seek_cb; + } + else + { + // Read from memory... + font->file_fd = -1; + font->data = (const char *)data; + font->data_size = datasize; + font->read_cb = mem_read_cb; + font->seek_cb = mem_seek_cb; + } + + // Read the table of contents and the identifying names... + if (!read_table(font)) + goto error; + + TTF_DEBUG("create_font: num_entries=%d\n", font->table.num_entries); + + if (!read_names(font)) + goto error; + + TTF_DEBUG("create_font: num_names=%d\n", font->names.num_names); + + // Copy key font meta data strings... + font->copyright = copy_name(font, TTF_OFF_Copyright); + font->family = copy_name(font, TTF_OFF_FontFamily); + font->postscript_name = copy_name(font, TTF_OFF_PostScriptName); + font->version = copy_name(font, TTF_OFF_FontVersion); + + if (read_post(font, &post)) + { + font->italic_angle = post.italicAngle; + font->is_fixed = post.isFixedPitch != 0; + } + + TTF_DEBUG("create_font: copyright=\"%s\"\n", font->copyright); + TTF_DEBUG("create_font: family=\"%s\"\n", font->family); + TTF_DEBUG("create_font: postscript_name=\"%s\"\n", font->postscript_name); + TTF_DEBUG("create_font: version=\"%s\"\n", font->version); + TTF_DEBUG("create_font: italic_angle=%g\n", font->italic_angle); + TTF_DEBUG("create_font: is_fixed=%s\n", font->is_fixed ? "true" : "false"); + + if (!read_cmap(font)) + goto error; + + if (!read_head(font, &head)) + goto error; + + font->units = (float)head.unitsPerEm; + font->x_max = head.xMax; + font->x_min = head.xMin; + font->y_max = head.yMax; + font->y_min = head.yMin; + + if (head.macStyle & TTF_OFF_macStyle_Italic) + { + if (font->postscript_name && strstr(font->postscript_name, "Oblique")) + font->style = TTF_STYLE_OBLIQUE; + else + font->style = TTF_STYLE_ITALIC; + } + else + font->style = TTF_STYLE_NORMAL; + + if (!read_hhea(font, &hhea)) + goto error; + + font->ascent = hhea.ascender; + font->descent = hhea.descender; + + if (read_maxp(font) < 0) + goto error; + + if (hhea.numberOfHMetrics > 0) + { + if ((widths = read_hmtx(font, &hhea)) == NULL) + goto error; + } + else + { + errorf(font, "Number of horizontal metrics is 0."); + goto error; + } + + if (read_os_2(font, &os_2)) + { + // Copy key values from OS/2 table... + static const ttf_stretch_t stretches[] = + { + TTF_STRETCH_ULTRA_CONDENSED, // ultra-condensed + TTF_STRETCH_EXTRA_CONDENSED, // extra-condensed + TTF_STRETCH_CONDENSED, // condensed + TTF_STRETCH_SEMI_CONDENSED, // semi-condensed + TTF_STRETCH_NORMAL, // normal + TTF_STRETCH_SEMI_EXPANDED, // semi-expanded + TTF_STRETCH_EXPANDED, // expanded + TTF_STRETCH_EXTRA_EXPANDED, // extra-expanded + TTF_STRETCH_ULTRA_EXPANDED // ultra-expanded + }; + + if (os_2.usWidthClass >= 1 && os_2.usWidthClass <= (int)(sizeof(stretches) / sizeof(stretches[0]))) + font->stretch = stretches[os_2.usWidthClass - 1]; + + font->weight = (short)os_2.usWeightClass; + font->cap_height = os_2.sCapHeight; + font->x_height = os_2.sxHeight; + } + else + { + // Default key values since there isn't an OS/2 table... + TTF_DEBUG("create_font: Unable to read OS/2 table.\n"); + + font->weight = 400; + } + + if (font->cap_height == 0) + font->cap_height = font->ascent; + + if (font->x_height == 0) + font->x_height = 3 * font->ascent / 5; + + // Build a sparse glyph widths table... + font->min_char = -1; + + for (i = 0; i < font->num_cmap; i ++) + { + if (font->cmap[i] >= 0) + { + int bin = (int)i / 256, // Sub-array bin + glyph = font->cmap[i]; // Glyph index + + // Update min/max... + if (font->min_char < 0) + font->min_char = (int)i; + + font->max_char = (int)i; + + // Allocate a sub-array as needed... + if (!font->widths[bin]) + font->widths[bin] = (_ttf_metric_t *)calloc(256, sizeof(_ttf_metric_t)); + + // Copy the width of the specified glyph or the last one if we are past + // the end of the table... + if (glyph >= hhea.numberOfHMetrics) + font->widths[bin][i & 255] = widths[hhea.numberOfHMetrics - 1]; + else + font->widths[bin][i & 255] = widths[glyph]; + } + +#ifdef DEBUG + if (i >= ' ' && i < 127 && font->widths[0]) + TTF_DEBUG("create_font: width['%c']=%d(%d)\n", (char)i, font->widths[0][i].width, font->widths[0][i].left_bearing); +#endif // DEBUG + } + + // Cleanup and return the font... + free(widths); + + return (font); + + // If we get here something bad happened... + error: + + free(widths); + ttfDelete(font); + + return (NULL); +} + + // // 'errorf()' - Show an error message. // @@ -1070,9 +1168,85 @@ errorf(ttf_t *font, // I - Font } -/* - * 'read_cmap()' - Read the cmap table, getting the Unicode mapping table. - */ +// +// 'fd_read_cb()' - Read from a file. +// + +static size_t // O - Number of bytes read +fd_read_cb(ttf_t *font, // I - Font + void *buffer, // I - Read buffer + size_t bytes) // I - Number of bytes to read +{ + ssize_t rbytes; // Bytes read + + + if ((rbytes = read(font->file_fd, buffer, bytes)) < 0) + return (0); + + return ((size_t)rbytes); +} + + +// +// 'fd_seek_cb()' - Seek in a file. +// + +static bool // O - `true` on success, `false` on failure +fd_seek_cb(ttf_t *font, // I - Font + size_t offset) // I - Offset in data +{ + return (lseek(font->file_fd, offset, SEEK_SET) == offset); +} + + +// +// 'mem_read_cb()' - Read from a memory buffer. +// + +static size_t // O - Number of bytes read +mem_read_cb(ttf_t *font, // I - Font + void *buffer, // I - Read buffer + size_t bytes) // I - Number of bytes to read +{ + size_t rbytes; // Bytes to copy + + + if (font->data_offset >= font->data_size) + return (0); + + if ((rbytes = font->data_size - font->data_offset) > bytes) + rbytes = bytes; + + memcpy(buffer, font->data + font->data_offset, rbytes); + font->data_offset += rbytes; + + return (rbytes); +} + + +// +// 'mem_seek_cb()' - Seek in a memory buffer. +// + +static bool // O - `true` on success, `false` on error +mem_seek_cb(ttf_t *font, // I - Font + size_t offset) // I - Offset in data +{ + if (offset >= font->data_size) + { + errno = ENXIO; + return (false); + } + + font->data_offset = offset; + + return (true); +} + + +// +// 'read_cmap()' - Read the cmap table, getting the Unicode mapping table. +// static bool // O - `true` on success, `false` on error read_cmap(ttf_t *font) // I - Font @@ -1219,7 +1393,7 @@ read_cmap(ttf_t *font) // I - Font return (false); } - if (read(font->fd, bmap, font->num_cmap) != (ssize_t)font->num_cmap) + if ((font->read_cb)(font, bmap, font->num_cmap) != font->num_cmap) { errorf(font, "Unable to read cmap table length at offset %u.", coffset); return (false); @@ -1646,9 +1820,9 @@ read_hhea(ttf_t *font, // I - Font } -/* - * 'read_hmtx()' - Read the horizontal metrics from the font. - */ +// +// 'read_hmtx()' - Read the horizontal metrics from the font. +// static _ttf_metric_t * // O - Array of glyph metrics read_hmtx(ttf_t *font, // I - Font @@ -1774,7 +1948,7 @@ read_names(ttf_t *font) // I - Font length -= (unsigned)offset; - if (read(font->fd, font->names.storage, length) < 0) + if ((font->read_cb)(font, font->names.storage, length) == 0) { errorf(font, "Unable to read name table: %s", strerror(errno)); return (false); @@ -1823,7 +1997,7 @@ read_os_2(ttf_t *font, // I - Font /* yStrikeoutOffset */ read_short(font); /* sFamilyClass */ read_short(font); /* panose[10] */ - if (read(font->fd, panose, sizeof(panose)) != (ssize_t)sizeof(panose)) + if ((font->read_cb)(font, panose, sizeof(panose)) != sizeof(panose)) return (false); /* ulUnicodeRange1 */ read_ulong(font); /* ulUnicodeRange2 */ read_ulong(font); @@ -1885,7 +2059,7 @@ read_short(ttf_t *font) // I - Font unsigned char buffer[2]; // Read buffer - if (read(font->fd, buffer, sizeof(buffer)) != sizeof(buffer)) + if ((font->read_cb)(font, buffer, sizeof(buffer)) != sizeof(buffer)) return (EOF); else if (buffer[0] & 0x80) return (((buffer[0] << 8) | buffer[1]) - 65536); @@ -1958,7 +2132,7 @@ read_table(ttf_t *font) // I - Font TTF_DEBUG("read_table: Offset for font %u is %u.\n", (unsigned)font->idx, temp); - if (lseek(font->fd, temp + 4, SEEK_SET) < 0) + if (!(font->seek_cb)(font, temp + 4)) { errorf(font, "Unable to seek to font %u: %s", (unsigned)font->idx, strerror(errno)); return (false); @@ -2034,7 +2208,7 @@ read_ulong(ttf_t *font) // I - Font unsigned char buffer[4]; // Read buffer - if (read(font->fd, buffer, sizeof(buffer)) != sizeof(buffer)) + if ((font->read_cb)(font, buffer, sizeof(buffer)) != sizeof(buffer)) return ((unsigned)EOF); else return ((unsigned)((buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3])); @@ -2051,7 +2225,7 @@ read_ushort(ttf_t *font) // I - Font unsigned char buffer[2]; // Read buffer - if (read(font->fd, buffer, sizeof(buffer)) != sizeof(buffer)) + if ((font->read_cb)(font, buffer, sizeof(buffer)) != sizeof(buffer)) return (EOF); else return ((buffer[0] << 8) | buffer[1]); @@ -2078,7 +2252,7 @@ seek_table(ttf_t *font, // I - Font if (current->tag == tag) { // Found it, seek and return... - if (lseek(font->fd, current->offset + offset, SEEK_SET) == (current->offset + offset)) + if ((font->seek_cb)(font, current->offset + offset)) { // Successful seek... return (current->length - offset); diff --git a/ttf.h b/ttf.h index 3ce3f31..ca519d5 100644 --- a/ttf.h +++ b/ttf.h @@ -3,7 +3,7 @@ // // https://github.com/michaelrsweet/ttf // -// Copyright © 2018-2024 by Michael R Sweet. +// Copyright © 2018-2025 by Michael R Sweet. // // Licensed under Apache License v2.0. See the file "LICENSE" for more // information. @@ -81,6 +81,7 @@ typedef struct ttf_rect_s // Bounding rectangle // extern ttf_t *ttfCreate(const char *filename, size_t idx, ttf_err_cb_t err_cb, void *err_data); +extern ttf_t *ttfCreateData(const void *data, size_t data_size, size_t idx, ttf_err_cb_t err_cb, void *err_data); extern void ttfDelete(ttf_t *font); extern int ttfGetAscent(ttf_t *font); extern ttf_rect_t *ttfGetBounds(ttf_t *font, ttf_rect_t *bounds);