15 Commits

17 changed files with 589 additions and 167 deletions

1
.gitignore vendored
View File

@ -13,4 +13,5 @@
/pdfiototext
/testpdfio
/testpdfio-*.pdf
/testttf
/x64

View File

@ -2,6 +2,22 @@ Changes in PDFio
================
v1.1.4 (December 3, 2023)
-------------------------
- Fixed detection of encrypted strings that are too short (Issue #52)
- Fixed a TrueType CMAP decoding bug.
- Fixed a text rendering issue for Asian text.
- Added a ToUnicode map for Unicode text to support text copying.
v1.1.3 (November 15, 2023)
--------------------------
- Fixed Unicode font support (Issue #16)
- Fixed missing initializer for 40-bit RC4 encryption (Issue #51)
v1.1.2 (October 10, 2023)
-------------------------

View File

@ -29,7 +29,7 @@ DSONAME =
LDFLAGS =
LIBS = -lm -lz
RANLIB = ranlib
VERSION = 1.1.2
VERSION = 1.1.4
prefix = /usr/local
@ -67,12 +67,14 @@ LIBOBJS = \
OBJS = \
$(LIBOBJS) \
pdfiototext.o \
testpdfio.o
testpdfio.o \
testttf.o
TARGETS = \
$(DSONAME) \
libpdfio.a \
pdfiototext \
testpdfio
testpdfio \
testttf
# Make everything
@ -136,6 +138,7 @@ install-shared:
# Test everything
test: testpdfio
./testttf 2>test.log
./testpdfio 2>test.log
valgrind: testpdfio
@ -182,10 +185,16 @@ testpdfio: testpdfio.o libpdfio.a
echo Linking $@...
$(CC) $(LDFLAGS) $(COMMONFLAGS) -o $@ testpdfio.o libpdfio.a $(LIBS)
# TTF test program
testttf: ttf.o testttf.o
echo Linking $@...
$(CC) $(LDFLAGS) $(COMMONFLAGS) -o testttf ttf.o testttf.o $(LIBS)
# Dependencies
$(OBJS): pdfio.h pdfio-private.h Makefile
pdfio-content.o: pdfio-content.h ttf.h
testttf.o: ttf.h
ttf.o: ttf.h
# Make documentation using Codedoc <https://www.msweet.org/codedoc>

View File

@ -1,4 +1,4 @@
.TH pdfio 3 "pdf read/write library" "2023-10-06" "pdf read/write library"
.TH pdfio 3 "pdf read/write library" "2023-11-15" "pdf read/write library"
.SH NAME
pdfio \- pdf read/write library
.SH Introduction

View File

@ -1,13 +1,13 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<title>PDFio Programming Manual v1.1.2</title>
<title>PDFio Programming Manual v1.1.3</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<meta name="generator" content="codedoc v3.7">
<meta name="author" content="Michael R Sweet">
<meta name="language" content="en-US">
<meta name="copyright" content="Copyright © 2021-2023 by Michael R Sweet">
<meta name="version" content="1.1.2">
<meta name="version" content="1.1.3">
<style type="text/css"><!--
body {
background: white;
@ -245,7 +245,7 @@ span.string {
<body>
<div class="header">
<p><img class="title" src="pdfio-512.png"></p>
<h1 class="title">PDFio Programming Manual v1.1.2</h1>
<h1 class="title">PDFio Programming Manual v1.1.3</h1>
<p>Michael R Sweet</p>
<p>Copyright © 2021-2023 by Michael R Sweet</p>
</div>

View File

@ -375,9 +375,9 @@ _pdfioFileSeek(pdfio_file_t *pdf, // I - PDF file
if (whence != SEEK_END && offset >= pdf->bufpos && offset < (pdf->bufpos + pdf->bufend - pdf->buffer))
{
// Yes, seek within existing buffer...
pdf->bufptr = pdf->buffer + offset - pdf->bufpos;
pdf->bufptr = pdf->buffer + (offset - pdf->bufpos);
PDFIO_DEBUG("_pdfioFileSeek: Seek within buffer, bufpos=%ld.\n", (long)pdf->bufpos);
PDFIO_DEBUG("_pdfioFileSeek: buffer=%p, bufptr=%p, bufend=%p\n", pdf->buffer, pdf->bufptr, pdf->bufend);
PDFIO_DEBUG("_pdfioFileSeek: buffer=%p, bufptr=%p(<%02X%02X...>), bufend=%p\n", pdf->buffer, pdf->bufptr, pdf->bufptr[0] & 255, pdf->bufptr[1] & 255, pdf->bufend);
return (offset);
}

View File

@ -1289,13 +1289,13 @@ pdfioFileCreateFontObjFromFile(
pdfio_dict_t *dict, // Font dictionary
*desc, // Font descriptor
*file; // Font file dictionary
pdfio_obj_t *obj, // Font object
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
int fd; // File
int fd = -1; // File
unsigned char buffer[16384]; // Read buffer
ssize_t bytes; // Bytes read
@ -1324,48 +1324,32 @@ pdfioFileCreateFontObjFromFile(
// Create the font file dictionary and object...
if ((file = pdfioDictCreate(pdf)) == NULL)
{
ttfDelete(font);
close(fd);
return (NULL);
}
goto done;
pdfioDictSetName(file, "Filter", "FlateDecode");
if ((file_obj = pdfioFileCreateObj(pdf, file)) == NULL)
{
ttfDelete(font);
close(fd);
return (NULL);
}
goto done;
if ((st = pdfioObjCreateStream(file_obj, PDFIO_FILTER_FLATE)) == NULL)
{
ttfDelete(font);
close(fd);
return (NULL);
}
goto done;
while ((bytes = read(fd, buffer, sizeof(buffer))) > 0)
{
if (!pdfioStreamWrite(st, buffer, (size_t)bytes))
{
ttfDelete(font);
close(fd);
pdfioStreamClose(st);
return (NULL);
goto done;
}
}
close(fd);
fd = -1;
pdfioStreamClose(st);
// Create the font descriptor dictionary and object...
if ((bbox = pdfioArrayCreate(pdf)) == NULL)
{
ttfDelete(font);
return (NULL);
}
goto done;
ttfGetBounds(font, &bounds);
@ -1375,10 +1359,7 @@ pdfioFileCreateFontObjFromFile(
pdfioArrayAppendNumber(bbox, bounds.top);
if ((desc = pdfioDictCreate(pdf)) == NULL)
{
ttfDelete(font);
return (NULL);
}
goto done;
basefont = pdfioStringCreate(pdf, ttfGetPostScriptName(font));
@ -1397,56 +1378,65 @@ pdfioFileCreateFontObjFromFile(
pdfioDictSetNumber(desc, "StemV", ttfGetWeight(font) / 4 + 25);
if ((desc_obj = pdfioFileCreateObj(pdf, desc)) == NULL)
{
ttfDelete(font);
return (NULL);
}
goto done;
pdfioObjClose(desc_obj);
if (unicode)
{
// Unicode (CID) font...
pdfio_dict_t *cid2gid; // CIDToGIDMap dictionary
pdfio_obj_t *cid2gid_obj; // CIDToGIDMap object
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, // Looping var
start, // Start character
num_cmap; // Number of CMap entries
const int *cmap; // CMap entries
int glyph, // Current glyph
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)
{
ttfDelete(font);
return (NULL);
}
goto done;
#ifndef DEBUG
pdfioDictSetName(cid2gid, "Filter", "FlateDecode");
#endif // !DEBUG
if ((cid2gid_obj = pdfioFileCreateObj(pdf, cid2gid)) == NULL)
{
ttfDelete(font);
return (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
{
ttfDelete(font);
return (NULL);
}
goto done;
cmap = ttfGetCMap(font, &num_cmap);
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);
@ -1464,6 +1454,12 @@ pdfioFileCreateFontObjFromFile(
// Map to specified glyph...
*bufptr++ = (unsigned char)(cmap[i] >> 8);
*bufptr++ = (unsigned char)(cmap[i] & 255);
glyphs[cmap[i]] = i;
if (cmap[i] < min_glyph)
min_glyph = cmap[i];
if (cmap[i] > max_glyph)
max_glyph = cmap[i];
}
if (bufptr >= bufend)
@ -1472,8 +1468,7 @@ pdfioFileCreateFontObjFromFile(
if (!pdfioStreamWrite(st, buffer, (size_t)(bufptr - buffer)))
{
pdfioStreamClose(st);
ttfDelete(font);
return (NULL);
goto done;
}
bufptr = buffer;
@ -1486,30 +1481,102 @@ pdfioFileCreateFontObjFromFile(
if (!pdfioStreamWrite(st, buffer, (size_t)(bufptr - buffer)))
{
pdfioStreamClose(st);
ttfDelete(font);
return (NULL);
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> <FFFF>\n"
"endcodespacerange\n"
"1 beginbfrange\n"
"<0000> <FFFF> <0000>\n"
"endbfrange\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)
{
ttfDelete(font);
return (NULL);
}
goto done;
if ((sidict = pdfioDictCreate(pdf)) == NULL)
{
ttfDelete(font);
return (NULL);
}
// Width array
if ((w_array = pdfioArrayCreate(pdf)) == NULL)
goto done;
// CIDSystemInfo mapping to Adobe UCS2 v0 (Unicode)
pdfioDictSetString(sidict, "Registry", "Adobe");
pdfioDictSetString(sidict, "Ordering", "Identity-H");
pdfioDictSetNumber(sidict, "Supplement", 0);
for (start = 0, w0 = ttfGetWidth(font, 0), i = 1; i < 65536; start = i, w0 = w1, i ++)
{
while (i < 65536 && (w1 = ttfGetWidth(font, i)) == w0)
i ++;
if ((i - start) > 1)
{
// Encode a repeating sequence...
pdfioArrayAppendNumber(w_array, start);
pdfioArrayAppendNumber(w_array, i - 1);
pdfioArrayAppendNumber(w_array, w0);
}
else
{
// Encode a non-repeating sequence...
pdfioArrayAppendNumber(w_array, start);
if ((temp_array = pdfioArrayCreate(pdf)) == NULL)
goto done;
pdfioArrayAppendNumber(temp_array, w0);
for (w0 = w1, i ++; i < 65536; w0 = w1, i ++)
{
if ((w1 = ttfGetWidth(font, i)) == w0 && i < 65535)
break;
pdfioArrayAppendNumber(temp_array, w0);
}
if (i == 65536)
pdfioArrayAppendNumber(temp_array, w0);
else
i --;
pdfioArrayAppendArray(w_array, temp_array);
}
}
// Then the dictionary for the CID base font...
pdfioDictSetName(type2, "Type", "Font");
@ -1518,56 +1585,41 @@ pdfioFileCreateFontObjFromFile(
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)
{
ttfDelete(font);
return (NULL);
}
goto done;
pdfioObjClose(type2_obj);
// Create a Type 0 font object...
if ((descendants = pdfioArrayCreate(pdf)) == NULL)
{
ttfDelete(font);
return (NULL);
}
goto done;
pdfioArrayAppendObj(descendants, type2_obj);
if ((dict = pdfioDictCreate(pdf)) == NULL)
{
ttfDelete(font);
return (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)
return (NULL);
pdfioObjClose(obj);
if ((obj = pdfioFileCreateObj(pdf, dict)) != NULL)
pdfioObjClose(obj);
}
else
{
// Simple (CP1282 or custom encoding) 8-bit font...
if (ttfGetMaxChar(font) >= 255 && !pdf->cp1252_obj && !create_cp1252(pdf))
{
ttfDelete(font);
return (NULL);
}
goto done;
// Create a TrueType font object...
if ((dict = pdfioDictCreate(pdf)) == NULL)
{
ttfDelete(font);
return (NULL);
}
goto done;
pdfioDictSetName(dict, "Type", "Font");
pdfioDictSetName(dict, "Subtype", "TrueType");
@ -1577,15 +1629,15 @@ pdfioFileCreateFontObjFromFile(
pdfioDictSetObj(dict, "FontDescriptor", desc_obj);
if ((obj = pdfioFileCreateObj(pdf, dict)) == NULL)
{
ttfDelete(font);
return (NULL);
}
pdfioObjClose(obj);
if ((obj = pdfioFileCreateObj(pdf, dict)) != NULL)
pdfioObjClose(obj);
}
done:
if (fd >= 0)
close(fd);
ttfDelete(font);
return (obj);
@ -3000,7 +3052,7 @@ write_string(pdfio_stream_t *st, // I - Stream
// Start the string...
if (!pdfioStreamPuts(st, unicode ? "<FEFF" : "("))
if (!pdfioStreamPuts(st, unicode ? "<" : "("))
return (false);
// Loop through the string, handling UTF-8 as needed...

View File

@ -1,7 +1,7 @@
//
// Cryptographic support functions for PDFio.
//
// Copyright © 2021 by Michael R Sweet.
// Copyright © 2021-2023 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
@ -446,10 +446,18 @@ _pdfio_crypto_cb_t // O - Decryption callback or `NULL` for none
// Initialize the RC4 context using 40 bits of the digest...
_pdfioCryptoRC4Init(&ctx->rc4, digest, 5);
*ivlen = 0;
return ((_pdfio_crypto_cb_t)_pdfioCryptoRC4Crypt);
case PDFIO_ENCRYPTION_RC4_128 :
case PDFIO_ENCRYPTION_AES_128 :
if (*ivlen < 16)
{
*ivlen = 0;
_pdfioFileError(pdf, "Value too short for AES encryption.");
return (NULL);
}
case PDFIO_ENCRYPTION_RC4_128 :
// Copy the key data for the MD5 hash.
memcpy(data, pdf->file_key, sizeof(pdf->file_key));
data[16] = (uint8_t)obj->number;

View File

@ -1514,6 +1514,7 @@ load_obj_stream(pdfio_obj_t *obj) // I - Object to load
// Skip offset
_pdfioTokenGet(&tb, buffer, sizeof(buffer));
PDFIO_DEBUG("load_obj_stream: %ld at offset %s\n", (long)number, buffer);
}
if (!buffer[0])
@ -1865,8 +1866,18 @@ load_xref(
break;
}
if (i >= num_sobjs && num_sobjs < (sizeof(sobjs) / sizeof(sobjs[0])))
sobjs[num_sobjs ++] = (size_t)offset;
if (i >= num_sobjs)
{
if (num_sobjs < (sizeof(sobjs) / sizeof(sobjs[0])))
{
sobjs[num_sobjs ++] = (size_t)offset;
}
else
{
_pdfioFileError(pdf, "Too many object streams.");
return (false);
}
}
}
else if (!current)
{

View File

@ -452,6 +452,7 @@ _pdfioStreamOpen(pdfio_obj_t *obj, // I - Object
return (NULL);
}
PDFIO_DEBUG("_pdfioStreamOpen: ivlen=%d\n", (int)ivlen);
if (ivlen > 0)
_pdfioFileConsume(st->pdf, ivlen);
@ -567,6 +568,7 @@ _pdfioStreamOpen(pdfio_obj_t *obj, // I - Object
else
st->predictor = _PDFIO_PREDICTOR_NONE;
PDFIO_DEBUG("_pdfioStreamOpen: pos=%ld\n", (long)_pdfioFileTell(st->pdf));
if (sizeof(st->cbuffer) > st->remaining)
rbytes = _pdfioFileRead(st->pdf, st->cbuffer, st->remaining);
else

View File

@ -383,7 +383,10 @@ _pdfioValueRead(pdfio_file_t *pdf, // I - PDF file
return (false);
}
cb = _pdfioCryptoMakeReader(pdf, obj, &ctx, v->value.binary.data, &ivlen);
ivlen = v->value.binary.datalen;
if ((cb = _pdfioCryptoMakeReader(pdf, obj, &ctx, v->value.binary.data, &ivlen)) == NULL)
return (false);
templen = (cb)(&ctx, temp, v->value.binary.data + ivlen, v->value.binary.datalen - ivlen);
// Copy the decrypted string back to the value and adjust the length...

View File

@ -23,7 +23,7 @@ extern "C" {
// Version number...
//
# define PDFIO_VERSION "1.1.2"
# define PDFIO_VERSION "1.1.4"
//

View File

@ -3,7 +3,7 @@
<metadata>
<id>pdfio_native</id>
<title>PDFio Library for VS2019+</title>
<version>1.1.2</version>
<version>1.1.4</version>
<authors>Michael R Sweet</authors>
<owners>michaelrsweet</owners>
<projectUrl>https://github.com/michaelrsweet/pappl</projectUrl>
@ -16,7 +16,7 @@
<copyright>Copyright © 2019-2023 by Michael R Sweet</copyright>
<tags>pdf file native</tags>
<dependencies>
<dependency id="pdfio_native.redist" version="1.1.2" />
<dependency id="pdfio_native.redist" version="1.1.4" />
<dependency id="zlib_native.redist" version="1.2.11" />
</dependencies>
</metadata>

View File

@ -3,7 +3,7 @@
<metadata>
<id>pdfio_native.redist</id>
<title>PDFio Library for VS2019+</title>
<version>1.1.2</version>
<version>1.1.4</version>
<authors>Michael R Sweet</authors>
<owners>michaelrsweet</owners>
<projectUrl>https://github.com/michaelrsweet/pappl</projectUrl>

View File

@ -44,7 +44,7 @@ static int verify_image(pdfio_file_t *pdf, size_t number);
static int write_alpha_test(pdfio_file_t *pdf, int number, pdfio_obj_t *font);
static int write_color_patch(pdfio_stream_t *st, bool device);
static int write_color_test(pdfio_file_t *pdf, int number, pdfio_obj_t *font);
static int write_font_test(pdfio_file_t *pdf, int number, pdfio_obj_t *font, bool unicode);
static int write_font_test(pdfio_file_t *pdf, int number, pdfio_obj_t *font, const char *textfontfile, bool unicode);
static int write_header_footer(pdfio_stream_t *st, const char *title, int number);
static pdfio_obj_t *write_image_object(pdfio_file_t *pdf, _pdfio_predictor_t predictor);
static int write_images_test(pdfio_file_t *pdf, int number, pdfio_obj_t *font);
@ -455,7 +455,7 @@ do_test_file(const char *filename, // I - PDF filename
}
}
printf(" Page #%d is %gx%g.\n", (int)n + 1, media_box.x2, media_box.y2);
printf(" Page #%d (obj %d) is %gx%g.\n", (int)n + 1, (int)pdfioObjGetNumber(obj), media_box.x2, media_box.y2);
}
}
@ -2229,14 +2229,19 @@ write_color_test(pdfio_file_t *pdf, // I - PDF file
//
static int // O - 1 on failure, 0 on success
write_font_test(pdfio_file_t *pdf, // I - PDF file
int number, // I - Page number
pdfio_obj_t *font, // I - Page number font
bool unicode) // I - Use Unicode font?
write_font_test(
pdfio_file_t *pdf, // I - PDF file
int number, // I - Page number
pdfio_obj_t *font, // I - Page number font
const char *textfontfile, // I - Text font file
bool unicode) // I - Use Unicode font?
{
pdfio_dict_t *dict; // Page dictionary
pdfio_stream_t *st; // Page contents stream
pdfio_obj_t *opensans; // OpenSans-Regular font
pdfio_obj_t *textfont; // Text font
char title[256], // Page title
textname[256], // Name of text font
*ptr; // Pointer into name
int i; // Looping var
static const char * const welcomes[] =// "Welcome" in many languages
{
@ -2387,28 +2392,16 @@ write_font_test(pdfio_file_t *pdf, // I - PDF file
"Märr-ŋamathirri",
"Ẹ ku abọ",
"Kíimak 'oolal",
"Ngiyakwemukela"
"Ngiyakwemukela",
"いらっしゃいませ"
};
#if 0
if (unicode)
{
fputs("pdfioFileCreateFontObjFromFile(NotoSansJP-Regular.otf): ", stdout);
if ((opensans = pdfioFileCreateFontObjFromFile(pdf, "testfiles/NotoSansJP-Regular.otf", true)) != NULL)
puts("PASS");
else
return (1);
}
printf("pdfioFileCreateFontObjFromFile(%s): ", textfontfile);
if ((textfont = pdfioFileCreateFontObjFromFile(pdf, textfontfile, unicode)) != NULL)
puts("PASS");
else
#endif // 0
{
fputs("pdfioFileCreateFontObjFromFile(OpenSans-Regular.ttf): ", stdout);
if ((opensans = pdfioFileCreateFontObjFromFile(pdf, "testfiles/OpenSans-Regular.ttf", unicode)) != NULL)
puts("PASS");
else
return (1);
}
return (1);
fputs("pdfioDictCreate: ", stdout);
if ((dict = pdfioDictCreate(pdf)) != NULL)
@ -2423,7 +2416,7 @@ write_font_test(pdfio_file_t *pdf, // I - PDF file
return (1);
fputs("pdfioPageDictAddFont(F2): ", stdout);
if (pdfioPageDictAddFont(dict, "F2", opensans))
if (pdfioPageDictAddFont(dict, "F2", textfont))
puts("PASS");
else
return (1);
@ -2435,7 +2428,21 @@ write_font_test(pdfio_file_t *pdf, // I - PDF file
else
return (1);
if (write_header_footer(st, unicode ? "Unicode TrueType Font Test" : "CP1252 TrueType Font Test", number))
if ((ptr = strrchr(textfontfile, '/')) != NULL)
strncpy(textname, ptr + 1, sizeof(textname) - 1);
else
strncpy(textname, textfontfile, sizeof(textname) - 1);
textname[sizeof(textname) - 1] = '\0';
if ((ptr = strrchr(textname, '.')) != NULL)
*ptr = '\0';
if (unicode)
snprintf(title, sizeof(title), "Unicode %s Font Test", textname);
else
snprintf(title, sizeof(title), "CP1252 %s Font Test", textname);
if (write_header_footer(st, title, number))
goto error;
fputs("pdfioContentTextBegin(): ", stdout);
@ -3390,14 +3397,17 @@ write_unit_file(
return (1);
// Test TrueType fonts...
if (write_font_test(outpdf, 9, helvetica, false))
if (write_font_test(outpdf, 9, helvetica, "testfiles/OpenSans-Regular.ttf", false))
return (1);
if (write_font_test(outpdf, 10, helvetica, true))
if (write_font_test(outpdf, 10, helvetica, "testfiles/OpenSans-Regular.ttf", true))
return (1);
if (write_font_test(outpdf, 11, helvetica, "testfiles/NotoSansJP-Regular.otf", true))
return (1);
// Print this text file...
if (write_text_test(outpdf, 11, helvetica, "README.md"))
if (write_text_test(outpdf, 12, helvetica, "README.md"))
return (1);
fputs("pdfioFileGetNumPages: ", stdout);

304
testttf.c Normal file
View File

@ -0,0 +1,304 @@
//
// Unit test program for TTF library
//
// https://github.com/michaelrsweet/ttf
//
// Copyright © 2018-2023 by Michael R Sweet.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
//
// Usage:
//
// ./testttf [FILENAME]
//
#include <stdio.h>
#include "ttf.h"
//
// Local functions...
//
static void error_cb(void *data, const char *message);
static int test_font(const char *filename);
//
// 'main()' - Main entry for unit tests.
//
int // O - Exit status
main(int argc, // I - Number of command-line arguments
char *argv[]) // I - Command-line arguments
{
int i; // Looping var
int errors = 0; // Number of errors
if (argc > 1)
{
for (i = 1; i < argc; i ++)
errors += test_font(argv[i]);
}
else
{
// Test with the bundled TrueType files...
errors += test_font("testfiles/OpenSans-Bold.ttf");
errors += test_font("testfiles/OpenSans-Regular.ttf");
errors += test_font("testfiles/NotoSansJP-Regular.otf");
}
if (!errors)
puts("\nALL TESTS PASSED");
else
printf("\n%d TEST(S) FAILED\n", errors);
return (errors);
}
//
// 'error_cb()' - Error callback.
//
static void
error_cb(void *data, // I - User data (not used)
const char *message) // I - Message string
{
fprintf(stderr, "FAIL (%s)\n", message);
}
//
// 'test_font()' - Test a font file.
//
static int // O - Number of errors
test_font(const char *filename) // I - Font filename
{
int i, // Looping var
errors = 0; // Number of errors
ttf_t *font; // Font
const char *value; // Font (string) value
int intvalue; // Font (integer) value
float realvalue; // Font (real) value
ttf_rect_t bounds; // Bounds
ttf_rect_t extents; // Extents
size_t num_fonts; // Number of fonts
ttf_style_t style; // Font style
ttf_weight_t weight; // Font weight
static const char * const stretches[] =
{ // Font stretch strings
"TTF_STRETCH_NORMAL", // normal
"TTF_STRETCH_ULTRA_CONDENSED", // ultra-condensed
"TTF_STRETCH_EXTRA_CONDENSED", // extra-condensed
"TTF_STRETCH_CONDENSED", // condensed
"TTF_STRETCH_SEMI_CONDENSED", // semi-condensed
"TTF_STRETCH_SEMI_EXPANDED", // semi-expanded
"TTF_STRETCH_EXPANDED", // expanded
"TTF_STRETCH_EXTRA_EXPANDED", // extra-expanded
"TTF_STRETCH_ULTRA_EXPANDED" // ultra-expanded
};
static const char * const strings[] = // Test strings
{
"Hello, World!", // English
"مرحبا بالعالم!", // Arabic
"Bonjour le monde!", // French
"Γειά σου Κόσμε!", // Greek
"שלום עולם!", // Hebrew
"Привет мир!", // Russian
"こんにちは世界!" // Japanese
};
static const char * const styles[] = // Font style names
{
"TTF_STYLE_NORMAL",
"TTF_STYLE_ITALIC",
"TTF_STYLE_OBLIQUE"
};
printf("ttfCreate(\"%s\"): ", filename);
if ((font = ttfCreate(filename, 0, error_cb, NULL)) != NULL)
puts("PASS");
else
errors ++;
fputs("ttfGetAscent: ", stdout);
if ((intvalue = ttfGetAscent(font)) > 0)
{
printf("PASS (%d)\n", intvalue);
}
else
{
printf("FAIL (%d)\n", intvalue);
errors ++;
}
fputs("ttfGetBounds: ", stdout);
if (ttfGetBounds(font, &bounds))
{
printf("PASS (%g %g %g %g)\n", bounds.left, bounds.bottom, bounds.right, bounds.top);
}
else
{
puts("FAIL");
errors ++;
}
fputs("ttfGetCapHeight: ", stdout);
if ((intvalue = ttfGetCapHeight(font)) > 0)
{
printf("PASS (%d)\n", intvalue);
}
else
{
printf("FAIL (%d)\n", intvalue);
errors ++;
}
fputs("ttfGetCopyright: ", stdout);
if ((value = ttfGetCopyright(font)) != NULL)
{
printf("PASS (%s)\n", value);
}
else
{
puts("WARNING (no copyright found)");
}
for (i = 0; i < (int)(sizeof(strings) / sizeof(strings[0])); i ++)
{
printf("ttfGetExtents(\"%s\"): ", strings[i]);
if (ttfGetExtents(font, 12.0f, strings[i], &extents))
{
printf("PASS (%.1f %.1f %.1f %.1f)\n", extents.left, extents.bottom, extents.right, extents.top);
}
else
{
puts("FAIL");
errors ++;
}
}
fputs("ttfGetFamily: ", stdout);
if ((value = ttfGetFamily(font)) != NULL)
{
printf("PASS (%s)\n", value);
}
else
{
puts("FAIL");
errors ++;
}
fputs("ttfGetItalicAngle: ", stdout);
if ((realvalue = ttfGetItalicAngle(font)) >= -180.0 && realvalue <= 180.0)
{
printf("PASS (%g)\n", realvalue);
}
else
{
printf("FAIL (%g)\n", realvalue);
errors ++;
}
fputs("ttfGetNumFonts: ", stdout);
if ((num_fonts = ttfGetNumFonts(font)) > 0)
{
printf("PASS (%u)\n", (unsigned)num_fonts);
}
else
{
puts("FAIL");
errors ++;
}
fputs("ttfGetPostScriptName: ", stdout);
if ((value = ttfGetPostScriptName(font)) != NULL)
{
printf("PASS (%s)\n", value);
}
else
{
puts("FAIL");
errors ++;
}
fputs("ttfGetStretch: ", stdout);
if ((intvalue = (int)ttfGetStretch(font)) >= TTF_STRETCH_NORMAL && intvalue <= TTF_STRETCH_ULTRA_EXPANDED)
{
printf("PASS (%s)\n", stretches[intvalue]);
}
else
{
printf("FAIL (%d)\n", intvalue);
errors ++;
}
fputs("ttfGetStyle: ", stdout);
if ((style = ttfGetStyle(font)) >= TTF_STYLE_NORMAL && style <= TTF_STYLE_ITALIC)
{
printf("PASS (%s)\n", styles[style]);
}
else
{
puts("FAIL");
errors ++;
}
fputs("ttfGetVersion: ", stdout);
if ((value = ttfGetVersion(font)) != NULL)
{
printf("PASS (%s)\n", value);
}
else
{
puts("FAIL");
errors ++;
}
fputs("ttfGetWeight: ", stdout);
if ((weight = ttfGetWeight(font)) >= 0)
{
printf("PASS (%u)\n", (unsigned)weight);
}
else
{
puts("FAIL");
errors ++;
}
fputs("ttfGetWidth(' '): ", stdout);
if ((intvalue = ttfGetWidth(font, ' ')) > 0)
{
printf("PASS (%d)\n", intvalue);
}
else
{
printf("FAIL (%d)\n", intvalue);
errors ++;
}
fputs("ttfGetXHeight: ", stdout);
if ((intvalue = ttfGetXHeight(font)) > 0)
{
printf("PASS (%d)\n", intvalue);
}
else
{
printf("FAIL (%d)\n", intvalue);
errors ++;
}
fputs("ttfIsFixedPitch: ", stdout);
if (ttfIsFixedPitch(font))
puts("PASS (true)");
else
puts("PASS (false)");
ttfDelete(font);
return (errors);
}

52
ttf.c
View File

@ -71,7 +71,7 @@ typedef __int64 ssize_t; // POSIX type not present on Windows...
//
// DEBUG is typically defined for debug builds. TTF_DEBUG maps to printf when
// DEBUG is typically defined for debug builds. TTF_DEBUG maps to fprintf when
// DEBUG is defined and is a no-op otherwise...
//
@ -448,7 +448,7 @@ ttfCreate(const char *filename, // I - Filename
font->cap_height = font->ascent;
if (font->x_height == 0)
font->x_height = 3 * font->ascent / 5;
font->x_height = 3 * font->ascent / 5;
// Build a sparse glyph widths table...
font->min_char = -1;
@ -477,6 +477,11 @@ ttfCreate(const char *filename, // I - Filename
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...
@ -687,9 +692,6 @@ ttfGetExtents(
ch = *s++;
}
// Issue #1: Offset past ".notdef"...
ch ++;
// Find its width...
if ((widths = font->widths[ch / 256]) != NULL)
{
@ -736,17 +738,6 @@ ttfGetFamily(ttf_t *font) // I - Font
}
//
// 'ttfIsFixedPitch()' - Determine whether a font is fixedpitch.
//
bool // O - `true` if fixed pitch, `false` otherwise
ttfIsFixedPitch(ttf_t *font) // I - Font
{
return (font ? font->is_fixed : false);
}
//
// 'ttfGetItalicAngle()' - Get the italic angle.
//
@ -855,13 +846,14 @@ int // O - Width in 1000ths
ttfGetWidth(ttf_t *font, // I - Font
int ch) // I - Unicode character
{
int bin = ch >> 8; // Bin in widths array
int bin = ch >> 8; // Bin in widths array
// Range check input...
if (!font || ch < ' ' || ch == 0x7f)
return (0);
else if (font->widths[bin])
if (font->widths[bin])
return ((int)(1000.0f * font->widths[bin][ch & 255].width / font->units));
else if (font->widths[0]) // .notdef
return ((int)(1000.0f * font->widths[0][0].width / font->units));
@ -881,6 +873,17 @@ ttfGetXHeight(ttf_t *font) // I - Font
}
//
// 'ttfIsFixedPitch()' - Determine whether a font is fixedpitch.
//
bool // O - `true` if fixed pitch, `false` otherwise
ttfIsFixedPitch(ttf_t *font) // I - Font
{
return (font ? font->is_fixed : false);
}
//
// 'copy_name()' - Copy a name string from a font.
//
@ -1279,7 +1282,7 @@ read_cmap(ttf_t *font) // I - Font
// Based on the end code of the segent table, allocate space for the
// uncompressed cmap table...
segCount --; // Last segment is not used (sigh)
// segCount --; // Last segment is not used (sigh)
font->num_cmap = segments[segCount - 1].endCode + 1;
font->cmap = cmapptr = (int *)malloc(font->num_cmap * sizeof(int));
@ -1304,17 +1307,18 @@ read_cmap(ttf_t *font) // I - Font
{
// Use an "obscure indexing trick" (words from the spec, not
// mine) to look up the glyph index...
temp = segment->idRangeOffset / 2 + ch - segment->startCode + seg - segCount - 1;
temp = segment->idRangeOffset / 2 - segCount + (ch - segment->startCode) + (segment - segments);
if (temp < 0 || temp >= numGlyphIdArray || !glyphIdArray[temp])
TTF_DEBUG("read_cmap: ch=%d, temp=%d\n", ch, temp);
if (temp < 0 || temp >= numGlyphIdArray)
glyph = -1;
else
glyph = ((glyphIdArray[temp] + segment->idDelta) & 65535) - 1;
glyph = (glyphIdArray[temp] + segment->idDelta) & 65535;
}
else
{
// Just use idDelta to compute a glyph index...
glyph = ((ch + segment->idDelta) & 65535) - 1;
glyph = (ch + segment->idDelta) & 65535;
}
cmapptr[ch] = glyph;
@ -1581,6 +1585,8 @@ read_hmtx(ttf_t *font, // I - Font
{
widths[i].width = (short)read_ushort(font);
widths[i].left_bearing = (short)read_short(font);
TTF_DEBUG("read_hmtx: widths[%d].width=%d, .left_bearing=%d\n", i, widths[i].width, widths[i].left_bearing);
}
return (widths);