summaryrefslogtreecommitdiff
path: root/Build/source/texk/dvipsk/bitmapenc.c
diff options
context:
space:
mode:
Diffstat (limited to 'Build/source/texk/dvipsk/bitmapenc.c')
-rw-r--r--Build/source/texk/dvipsk/bitmapenc.c690
1 files changed, 690 insertions, 0 deletions
diff --git a/Build/source/texk/dvipsk/bitmapenc.c b/Build/source/texk/dvipsk/bitmapenc.c
new file mode 100644
index 00000000000..ef2d868bc9c
--- /dev/null
+++ b/Build/source/texk/dvipsk/bitmapenc.c
@@ -0,0 +1,690 @@
+/*
+ * This file implements support for adding proper character encodings to
+ * bitmap fonts, so searching and copying text works better in PDFs
+ * generated by the dvips Postscript output that uses bitmap fonts.
+ *
+ * Written by Tomas Rokicki in June/July 2019. Public domain.
+ * The dvips-all.enc file used here is also public domain.
+ *
+ * It turns out we need to do more than just add encodings to the fonts
+ * for most PDF viewers, so this file also takes care of those issues.
+ */
+/*
+ * Our first concern is finding and reading encoding files for bitmap
+ * fonts, and, if such cannot be found, determining a reasonable
+ * alternative.
+ *
+ * We could use the code in writet1 to read an encoding file, but
+ * we would need to make some messy changes for that to work, and we
+ * really want this to be a minimal impact to existing code (especially
+ * code such as write_t1 that we don't have a good handle on).
+ *
+ * We want to deduplicate (combine identical) encodings to lower the
+ * memory and file size impact. But we have to be careful; if the
+ * document requires multiple sections, we need to redownload encodings
+ * just like we do fonts.
+ */
+#include <stdio.h>
+#include <string.h>
+#ifndef STANDALONE
+#include <kpathsea/config.h>
+#endif
+#include "dvips.h"
+#include "protos.h"
+/*
+ * Do we use this functionality? If 0, then no; if 1, then yes, but
+ * suppress warnings; if 2, then warn.
+ */
+int encodetype3 = 1 ;
+void bitmapencopt(int v) {
+ if (0 <= v && v <= 2)
+ encodetype3 = v ;
+ else
+ error("! Bad option to J") ;
+}
+/*
+ * TODO
+ *
+ * Initially we support standalone compilation for testing but
+ * this code will be ripped out before release.
+ */
+#ifdef STANDALONE
+#undef fopen
+#undef fclose
+#endif
+/*
+ * Reading the encodings.
+ *
+ * Encodings we read are returned as an array C strings. We do not
+ * attempt to parse the contents of the encodings, because they
+ * may contain additional dictionaries or other PostScript code
+ * that can possibly do more advanced things.
+ */
+#define MAX_LINE_LENGTH 256
+/*
+ * A structure for storing deduplicated encodings. These are deduplicated
+ * in dvips memory on load. We also store information on whether the
+ * encoding has been downloaded in a particular section. This is stored
+ * as a singly-linked list for all distinct encodings we have used so
+ * far.
+ */
+struct bmenc {
+ const char **enc ; // the encoding itself
+ int downloaded_seq ; // -1: not downloaded; else, sequence number
+ struct bmenc *next ; // next encoding in linear linked list
+ unsigned char existsbm[32] ; // a bitmap of characters with names
+} ;
+static struct bmenc *bmlist ;
+/*
+ * This structure maintains a relationship between a font name and a
+ * bitmap encoding. Again, linear search is used.
+ */
+struct bmfontenc {
+ const char *fontname ; // dvi font name
+ struct bmenc *enc ; // the encoding to use
+ struct bmfontenc *next ; // next encoding
+} ;
+static struct bmfontenc *bmfontenclist ;
+/*
+ * We load the default encodings for all fonts en masse from he
+ * dvips-all.enc file, and store them here. We use an array and
+ * sort the input for quicker search. We don't need to deduplicate
+ * because we assume whatever generated dvips-all.enc already did
+ * deduplication.
+ */
+static int numstatic ;
+static struct allfontenc {
+ const char *fontname ;
+ const char **enc ;
+} *bmfontarr ;
+/*
+ * While loading dvips-all.enc, we track the capacity of our allfontenc
+ * array, how many have names, the capacity in strings of our encoding
+ * that is being built up, and how many strings we still can store in
+ * the encoding built up.
+ */
+static int capstatic, namedstatic ;
+static int in_working_enc, working_enc_left ;
+const char **working_enc ;
+/*
+ * Add another fontname to the list (from the dvips-all.enc file).
+ * If needed expand the working arrays. If there is currently
+ * an encoding, that means that we need to first assign that
+ * encoding to the list of font names we have built up, so do
+ * that first. If the fontname passed is the null string, this is
+ * the last call done just to finish up the last encoding.
+ */
+static void add_fontname(const char *fontname) {
+ int i ;
+ if (in_working_enc) { // flush
+ const char **e = working_enc ;
+ while (numstatic < namedstatic)
+ bmfontarr[numstatic++].enc = e ;
+ working_enc += in_working_enc + 1 ;
+ working_enc_left -= in_working_enc + 1 ;
+ in_working_enc = 0 ;
+ }
+ if (fontname == 0)
+ return ;
+ if (namedstatic >= capstatic) {
+ int ncapstatic = capstatic * 2 + 20 ;
+ struct allfontenc *nbmfontarr =
+ (struct allfontenc *)mymalloc(sizeof(struct allfontenc) * ncapstatic) ;
+ if (capstatic) {
+ memcpy(nbmfontarr, bmfontarr, sizeof(struct allfontenc)*capstatic) ;
+ free(bmfontarr) ;
+ }
+ bmfontarr = nbmfontarr ;
+ for (i=capstatic; i<ncapstatic; i++) {
+ bmfontarr[i].fontname = 0 ;
+ bmfontarr[i].enc = 0 ;
+ }
+ capstatic = ncapstatic ;
+ }
+ bmfontarr[namedstatic].fontname = fontname ;
+ bmfontarr[namedstatic].enc = 0 ;
+ namedstatic++ ;
+}
+/*
+ * Add an encoding line to the working encoding; if needed, reallocate
+ * the working array.
+ */
+static void add_encline(const char *encline) {
+ if (in_working_enc + 1 >= working_enc_left) {
+ int i ;
+ int new_enc_size = 100 + 2 * in_working_enc ;
+ const char **new_working_enc =
+ (const char **)mymalloc(sizeof(const char **) * new_enc_size) ;
+ for (i=0; i<new_enc_size; i++)
+ new_working_enc[i] = 0 ;
+ for (i=0; i<in_working_enc; i++)
+ new_working_enc[i] = working_enc[i] ;
+ working_enc = new_working_enc ;
+ working_enc_left = new_enc_size - in_working_enc ;
+ }
+ working_enc[in_working_enc++] = encline ;
+}
+/*
+ * Parse the encoding file. If use_all flag is set then we are parsing
+ * the dvips_all file which can have multiple encodings, each preceded
+ * by a set of lines with font names followed by a colon character.
+ *
+ * If we are parsing them all, we build a sorted global list of entries
+ * for later lookup, and always return 0. Otherwise we just expect a
+ * single encoding and we store that away.
+ */
+static const char **parseencodingfile(FILE *f, int use_all) {
+ char encbuf[MAX_LINE_LENGTH+1] ;
+ size_t i ;
+ for (i=0; i<sizeof(encbuf); i++)
+ encbuf[i] = 0 ;
+ while (fgets(encbuf, MAX_LINE_LENGTH, f) != 0) {
+ char *p = encbuf ;
+ char *q = p + strlen(p) - 1 ;
+ while (q > p && *q < ' ') // kill line terminators
+ *q-- = 0 ;
+ q = p ;
+ while (*q && *q != ' ' && *q != ':')
+ q++ ;
+ if (use_all && *q == ':') { // looks like a font name
+ if (q[1] >= ' ' || !use_all)
+ error(
+ "! unexpected colon or extra stuff at end of line in encoding file?") ;
+ *q = 0 ;
+ add_fontname(strdup(p)) ;
+ } else {
+ add_encline(strdup(p)) ;
+ }
+ }
+ if (use_all)
+ add_fontname(0) ; // flushes last encoding
+ if (use_all) {
+ // sort the static fonts for quicker search; Shell sort
+ int h = 1 ;
+ while (h < numstatic)
+ h = 3 * h + 1 ;
+ while (h > 1) {
+ int i ;
+ h /= 3 ;
+ for (i=h; i<numstatic; i++) {
+ int j = i ;
+ while (j >= h &&
+ strcmp(bmfontarr[j-h].fontname, bmfontarr[j].fontname) > 0) {
+ const char *na = bmfontarr[j-h].fontname ;
+ const char **en = bmfontarr[j-h].enc ;
+ bmfontarr[j-h].fontname = bmfontarr[j].fontname ;
+ bmfontarr[j-h].enc = bmfontarr[j].enc ;
+ bmfontarr[j].fontname = na ;
+ bmfontarr[j].enc = en ;
+ j -= h ;
+ }
+ }
+ }
+ return 0 ;
+ } else {
+ const char **e = working_enc ;
+ if (e == 0)
+ error("! No lines in encoding?") ;
+ return e ;
+ }
+}
+/*
+ * Given a font name, find an encoding file. We prefix all encoding
+ * files with "dvips-" to distinguish these encodings from those used
+ * in the psfonts.map, since the syntax, requirements, and function
+ * is somewhat different.
+ */
+#define MAX_NAME_SIZE 256
+static FILE *bitmap_enc_search(const char *fontname) {
+ char namebuf[MAX_NAME_SIZE+1] ;
+ if (fontname == 0 || strlen(fontname) > 128)
+ error("! excessively long font name") ;
+ sprintf(namebuf, "dvips-%s.enc", fontname) ;
+#ifdef STANDALONE
+ return fopen(namebuf, FOPEN_RBIN_MODE) ;
+#else
+ return search(kpse_enc_format, namebuf, FOPEN_RBIN_MODE) ;
+#endif
+}
+/*
+ * Are two raw encodings identical? They must be exactly the same; there
+ * is no effort towards comparing semantics, just precise text.
+ */
+static int eqencoding(const char **a, const char **b) {
+ int i ;
+ if (a == b)
+ return 1 ;
+ for (i=0; a[i] != 0 && b[i] != 0; i++)
+ if (a[i] != b[i] && (a[i] == 0 || b[i] == 0 || strcmp(a[i], b[i]) != 0))
+ return 0 ;
+ return 1 ;
+}
+/*
+ * Try to parse an encoding to identify which names exist and which don't.
+ * If we can't trivially parse it we just mark all names as existing.
+ * We properly parse the following types of tokens:
+ * [ (at start) ] (at end) : ignored
+ * /name
+ * digits { /.notdef } repeat
+ *
+ * In all cases the spaces and linebreaks are arbitrary.
+ *
+ * The state machine works as follows:
+ *
+ * 'B': begin; expect [
+ * 'N': between entries; expect name or digits or ]
+ * 'E': finished (saw ]); expect nothing more
+ * '#': just saw digits; expect only {
+ * '{': just saw {; expect only name and should be /.notdef
+ * 'L': just saw a name inside {}; expect only }
+ * '}': just saw }; expect only repeat
+ */
+static int trytoparseenc(struct bmenc *bme) {
+ int i ;
+ const char **enc = bme->enc ;
+ const char *p ;
+ int seenchars = 0 ;
+ int num = 0 ;
+ char state = 'B' ;
+ for (i=0; i<32; i++)
+ bme->existsbm[i] = 255 ;
+ while (*enc != 0) {
+ p = *enc ;
+ enc++ ;
+ while (*p && *p <= ' ')
+ p++ ;
+ while (*p != 0) {
+ switch (state) {
+case 'B': if (*p != '[') return 0 ;
+ p++ ;
+ state = 'N' ;
+ break ;
+case 'N': if (*p == ']') {
+ p++ ;
+ state = 'E' ;
+ } else if (*p == '/') {
+ if (seenchars >= 256)
+ return 0 ;
+ if (strncmp(p, "/.notdef", 8) == 0 &&
+ (p[8] <= ' ' || strchr("{}[]<>()%/", p[8]) == 0)) {
+ bme->existsbm[seenchars>>3] &= ~(1<<(seenchars & 7)) ;
+ }
+ // see PostScript language reference manual syntax for this
+ p++ ;
+ while (*p > ' ' && strchr("{}[]<>()%/", *p) == 0)
+ p++ ;
+ seenchars++ ;
+ } else if ('0' <= *p && *p <= '9') {
+ num = 0 ;
+ while (num < 256 && '0' <= *p && *p <= '9')
+ num = 10 * num + *p++ - '0' ;
+ state = '#' ;
+ } else
+ return 0 ;
+ break ;
+case '#': if (*p != '{') return 0 ;
+ p++ ;
+ state = '{' ;
+ break ;
+case '{': if (strncmp(p, "/.notdef", 8) != 0)
+ return 0 ;
+ p += 8 ;
+ if (*p > ' ' && strchr("{}[]<>()%/", *p) == 0)
+ return 0 ;
+ while (num > 0) {
+ if (seenchars >= 256)
+ return 0 ;
+ bme->existsbm[seenchars>>3] &= ~(1<<(seenchars & 7)) ;
+ seenchars++ ;
+ num-- ;
+ }
+ state = 'L' ;
+ break ;
+case 'L': if (*p != '}')
+ return 0 ;
+ p++ ;
+ state = '}' ;
+ break ;
+case '}': if (strncmp(p, "repeat", 6) != 0)
+ return 0 ;
+ p += 6 ;
+ state = 'N' ;
+ break ;
+default:
+ error("! internal error in encoding vector parse") ;
+ }
+ while (*p && *p <= ' ')
+ p++ ;
+ }
+ }
+ if (seenchars != 256)
+ return 0 ;
+ return 1 ;
+}
+static void parseenc(struct bmenc *bme) {
+ int i ;
+ if (trytoparseenc(bme) == 0) {
+ printf("Failed to parse it.\n") ;
+ for (i=0; i<32; i++)
+ bme->existsbm[i] = 255 ;
+ }
+}
+/*
+ * Add an encoding to our list of deduplicated encodings.
+ */
+struct bmenc *addbmenc(const char **enc) {
+ struct bmenc *r = (struct bmenc *)mymalloc(sizeof(struct bmenc)) ;
+ r->downloaded_seq = -1 ;
+ r->enc = enc ;
+ r->next = bmlist ;
+ parseenc(r) ;
+ bmlist = r ;
+ return r ;
+}
+/*
+ * Given a particular encoding, walk through our encoding list and
+ * see if it already exists; if so, return the existing one.
+ * The set of distinct encodings in a particular document is
+ * expected to be small (a few dozen at most).
+ */
+static struct bmenc *deduplicateencoding(const char **enc) {
+ struct bmenc *p ;
+ for (p=bmlist; p!=0; p=p->next)
+ if (eqencoding(p->enc, enc))
+ return p ;
+ return addbmenc(enc) ;
+}
+/*
+ * Attempt to load an encoding; first, find the file, and then parse
+ * the file.
+ */
+static const char **bitmap_enc_load(const char *fontname, int use_all) {
+ FILE *f = bitmap_enc_search(use_all ? "all" : fontname) ;
+ if (f != 0) {
+ const char **r = parseencodingfile(f, use_all) ;
+ fclose(f) ;
+ return r ;
+ }
+ return 0 ;
+}
+/*
+ * Start a section: mark all encodings as not downloaded yet.
+ */
+static int curbmseq ;
+void bmenc_startsection() {
+ struct bmenc *p ;
+ for (p=bmlist; p!=0; p=p->next)
+ p->downloaded_seq = -1 ;
+ curbmseq = 0 ;
+}
+/*
+ * Try to find a font in the all encoding list. This list is sorted
+ * so we can use binary search.
+ */
+static const char **bitmap_all_find(const char *fontname) {
+ int ptr, bit;
+ if (numstatic == 0)
+ return 0 ;
+ ptr = 0 ;
+ bit = 1 ;
+ while ((bit << 1) < numstatic)
+ bit <<= 1 ;
+ while (bit > 0) {
+ int t = ptr + bit ;
+ if (t < numstatic && strcmp(fontname, bmfontarr[t].fontname) >= 0)
+ ptr = t ;
+ bit >>= 1 ;
+ }
+ if (strcmp(fontname, bmfontarr[ptr].fontname) == 0)
+ return bmfontarr[ptr].enc ;
+ else
+ return 0 ;
+}
+/*
+ * Download the encoding and set the sequence number.
+ */
+static void downloadenc(struct bmenc *enc) {
+ int fresh = 0 ;
+ char cmdbuf[16] ;
+ if (enc->downloaded_seq < 0) {
+ int i ;
+ newline() ;
+ for (i=0; enc->enc[i]; i++)
+ pslineout(enc->enc[i]) ;
+ enc->downloaded_seq = curbmseq++ ;
+ fresh = 1 ;
+ }
+ newline() ;
+ sprintf(cmdbuf, "/EN%d", enc->downloaded_seq) ;
+ if (fresh) {
+ cmdout("A") ;
+ psnameout(cmdbuf) ;
+ cmdout("X") ;
+ } else {
+ // we use load here specifically to defer execution until the
+ // new font dictionary is on the stack and being built.
+ psnameout(cmdbuf) ;
+ cmdout("load") ;
+ }
+}
+/*
+ * Send out the new encoding, font bounding box, and font matrix.
+ * If we cannot find an encoding file for this font, do what we
+ * used to do instead (don't give it an encoding or resize or
+ * rescale).
+ */
+static struct bmenc *getencoding_seq(const char *fontname) ;
+int downloadbmencoding(const char *name, double scale, fontdesctype *curfnt) {
+ int slop;
+ int i ;
+ int seq ;
+ int llx = curfnt->llx ;
+ int lly = curfnt->lly ;
+ int urx = curfnt->urx ;
+ int ury = curfnt->ury ;
+ struct bmenc *bme = getencoding_seq(name) ;
+ if (bme == 0)
+ return -1 ;
+ seq = bme->downloaded_seq ;
+/*
+ * Check that every character defined in the font has a name in the
+ * PostScript vector, and complain if this is not the case.
+ */
+ for (i=0; i<256 && i<curfnt->maxchars; i++) {
+ if ((curfnt->chardesc[i].flags2 & EXISTS) &&
+ !(bme->existsbm[i>>3] & (1 << (i & 7)))) {
+ fprintf_str(stderr,
+"Can't use PostScript encoding vector for font %s; character %d has no name.\n",
+ name, i) ;
+ return -1 ;
+ }
+ }
+ cmdout("IEn") ;
+ cmdout("S") ;
+ psnameout("/IEn") ;
+ cmdout("X") ;
+ cmdout("FBB") ;
+ cmdout("FMat") ;
+ psnameout("/FMat") ;
+ specialout('[') ;
+ doubleout(1.0/scale) ;
+ numout(0) ;
+ numout(0) ;
+ doubleout(-1.0/scale) ;
+ numout(0) ;
+ numout(0) ;
+ specialout(']') ;
+ cmdout("N") ;
+ psnameout("/FBB") ;
+ // we add a bit of slop here, because this is only used for
+ // highlighting, and in theory if the bounding box is too
+ // tight, on some RIPs, characters could be clipped.
+ slop = 1;
+ specialout('[') ;
+ numout(llx-slop) ;
+ numout(lly-slop) ;
+ numout(urx+slop) ;
+ numout(ury+slop) ;
+ specialout(']') ;
+ cmdout("N") ;
+ newline() ;
+ return seq ;
+}
+/*
+ * Finish up the downloaded encoding; scale the font, and restore the
+ * previous global definitions.
+ */
+void finishbitmapencoding(const char *name, double scale) {
+ psnameout(name) ;
+ cmdout("load") ;
+ numout(0) ;
+ cmdout(name+1) ;
+ cmdout("currentfont") ;
+ doubleout(scale) ;
+ cmdout("scalefont") ;
+ cmdout("put") ;
+ psnameout("/FMat") ;
+ cmdout("X") ;
+ psnameout("/FBB") ;
+ cmdout("X") ;
+ psnameout("/IEn") ;
+ cmdout("X") ;
+ newline();
+}
+/*
+ * We warn if we have to use a built-in encoding, and set this value to 1.
+ * We warn again if we cannot find a built-in encoding and have to
+ * default to previous behavior, and set this value to 2. In any case,
+ * we only issue warnings if the encodetype3 flag is set to a value greater
+ * than one; we don't want to bother folks about this new functionality.
+ */
+static int warned_about_missing_encoding = 0 ;
+/*
+ * Print a warning message.
+ */
+static void bmenc_warn(const char *fontname, const char *msg) {
+ if (encodetype3 > 1)
+ fprintf_str(stderr,
+ "dvips: Static bitmap font encoding for font %s (and others?): %s.\n",
+ fontname, msg) ;
+}
+/*
+ * About to download a font; find the encoding sequence number.
+ * If needed, download a new sequence. If we can't find one, use
+ * -1; this font may not work as well for copy/paste and text search.
+ */
+static int tried_all = 0 ; // have we tried to load dvips-all.enc
+static struct bmenc *getencoding_seq(const char *fontname) {
+ struct bmenc *enc = 0 ;
+ struct bmfontenc *p = bmfontenclist ;
+ for (; p!=0; p=p->next)
+ if (strcmp(fontname, p->fontname) == 0) {
+ enc = p->enc ;
+ if (enc == 0) // remember failures
+ return 0 ;
+ break ;
+ }
+ // not in list; try to load it from a file
+ if (enc == 0) {
+ const char **e = bitmap_enc_load(fontname, 0) ;
+ // did not find it; try to load it from the dvips-all list.
+ if (e == 0) {
+ if (!tried_all) {
+ bitmap_enc_load(fontname, 1) ;
+ tried_all = 1 ;
+ }
+ e = bitmap_all_find(fontname) ;
+ }
+ if (e)
+ enc = deduplicateencoding(e) ;
+ }
+ // did not find this in the list; add it to the list
+ if (p == 0) {
+ p = (struct bmfontenc *)mymalloc(sizeof(struct bmfontenc)) ;
+ p->fontname = strdup(fontname) ;
+ p->enc = enc ;
+ p->next = bmfontenclist ;
+// parseenc(enc) ;
+ bmfontenclist = p ;
+ }
+ if (enc == 0) {
+ if (warned_about_missing_encoding < 2) {
+ bmenc_warn(fontname, "no encoding found") ;
+ warned_about_missing_encoding = 2 ;
+ }
+ return 0 ; // don't download an encoding
+ }
+ downloadenc(enc) ;
+ return enc ;
+}
+#ifdef STANDALONE
+/*
+ * Standalone test code:
+ */
+void error(const char *s) {
+ fprintf(stderr, "Failed: %s\n", s) ;
+ if (*s == '!')
+ exit(0) ;
+}
+char *mymalloc(int sz) {
+ return (char *)malloc(sz) ;
+}
+int pos = 0 ;
+int idok = 1 ;
+const int MAXLINE = 75 ;
+void newline() {
+ printf("\n") ;
+ idok = 1 ;
+ pos = 0 ;
+}
+void doubleout(double f) {
+ printf("%g", f) ;
+ pos += 8 ;
+}
+void pslineout(const char *s) {
+ printf("%s\n", s) ;
+}
+void numout(int num) {
+ int len = 1 ;
+ int t = num / 10 ;
+ while (t>0) {
+ len++ ;
+ t /= 10 ;
+ }
+ if (pos + len >= MAXLINE)
+ newline() ;
+ if (!idok)
+ specialout(' ') ;
+ printf("%d", num) ;
+ pos += len ;
+ idok = 0 ;
+}
+void cmdout(const char *s) {
+ if (pos + strlen(s) >= MAXLINE)
+ newline() ;
+ if (*s != '/' && !idok)
+ specialout(' ') ;
+ printf("%s", s) ;
+ pos += strlen(s) ;
+ idok = 0 ;
+}
+void psnameout(const char *s) {
+ cmdout(s) ;
+}
+void specialout(char c) {
+ if (pos + 1 >= MAXLINE)
+ newline() ;
+ printf("%c", c) ;
+ pos++ ;
+ idok = 1 ;
+}
+int main(int argc, char *argv[]) {
+ bmenc_startsection() ;
+ for (int i=1; i<argc; i++) {
+ struct bmenc *r = getencoding_seq(argv[i]) ;
+ int seq = r ? r->downloaded_seq : -1 ;
+ printf("\n") ;
+ printf("Result for %s is %d\n", argv[i], seq) ;
+ }
+}
+#endif