diff --git a/EDITOR-PROTOCOL.md b/EDITOR-PROTOCOL.md index 2eb25f7f..9bbfc3c1 100644 --- a/EDITOR-PROTOCOL.md +++ b/EDITOR-PROTOCOL.md @@ -128,6 +128,24 @@ Check the filesystem for changes. This will reload and reprocess any changed fil Asking the window manager to keep TeXpresso window above the others, or not. This can be convenient to keep a TeXpresso window floating on top of the editor. (`t` and `nil` are the closest approximation of "true" and "false" in emacs-sexp). +```scheme +(register "path") +``` + +Pre-register a filename that the editor will provide later via `open`. When the engine tries to read this file and it is not yet available, the driver pauses the engine and emits `(lookup-file read promised "path")`. Once the editor sends `open` for the path, the engine resumes without restarting. + +```scheme +(pause) +(resume) +``` + +Suspend (or resume) engine stepping. While paused, TeXpresso keeps reading +stdin and applying editor commands (`open`, `register`, etc.) but does not +advance the TeX engine. Useful at startup with `-stream`: the editor can +prime the VFS via `(register)` and `(open)`, then send `(resume)` to begin +compilation — avoiding restarts from missing files. In `-stream` mode the +engine starts paused, so `(resume)` is required to begin compilation. + ```scheme (synctex-forward "path" line) ``` @@ -209,3 +227,17 @@ The paths are printed relative to the root file. They might be non-existent on t Right now, this is implemented by hooking into SyncTeX: - only text files are tracked (not graphics) - the indices printed are the SyncTex input indices; they should be attributed no other meaning than being monotonic and useful to detect backtracking occurrences + +### File lookups + +``` +(lookup-file kind status "path") +``` + +Output by TeXpresso when it tries to look up a file. +- `kind`: either `read` or `write`. +- `status`: either `successful`, `failed`, or `promised` if the editor had previously registered the file. +- `path`: the path to the file. + +If `status` is `promised`, document processing will be stuck until the editor fulfills the promise by sending a corresponding `(open "path" ...)` or `(open-base64 "path" ...)` command. +This can only happen if the editor had registered the path using `(register "path")`. diff --git a/doc/TIKZ-SUPPORT.md b/doc/TIKZ-SUPPORT.md new file mode 100644 index 00000000..ee87673b --- /dev/null +++ b/doc/TIKZ-SUPPORT.md @@ -0,0 +1,73 @@ +# TeXpresso TikZ Support — PR Summary + +This PR adds TikZ/pgf rendering support to TeXpresso. ~70 commits on branch `tikz` implement a PostScript interpreter and PDF content-stream handler that process the DVI specials emitted by PGF's drivers (dvips and dvipdfmx). + +## What Works + +### Path & Shape Rendering +`moveto`, `lineto`, `curveto`, `closepath`, `newpath`, rectangles (`pgfe`). All basic TikZ paths render: lines, curves (bézier, parabola), circles, ellipses, rectangles, arcs, grids, plot coordinates. + +### Fill & Stroke +`pgffill`, `pgfstr`, `pgfs`/`pgfS`, `pgfr`/`pgfR`, `pgfeofill`. `\fill`, `\draw`, `\filldraw` all work. Stroke keeps the path after fill so `\filldraw` can issue both. + +### Color +`setrgbcolor`, `setgray`, `setcmykcolor`, plus PGF's `pgffc`/`pgfsc` color-function scheme. Gray, RGB, and CMYK color spaces. Named xcolor colours resolve correctly. + +### Graphics State +`gsave`/`grestore` (`q`/`Q`), line width (`pgfw`, `w`), line join, line cap, miter limit. Dash patterns including long arrays (>4 elements) via `setdash`. + +### Transforms +PS `concat` (including `cm` in PDF content streams), `scale`, `translate`, `rotate`, `currentpoint`. CTM is built on the TeX page-position so that PGF coordinate arithmetic is correct. `\resizebox` and `\scalebox` from graphicx are supported. + +### Clipping +`W`/`W*` and PS `clip`/`eoclip`. Stacks correctly with `gsave`/`grestore`. + +### Text (Basic) +DVI text operators render node labels. Standard PDF 14 fonts are resolved lazily. The TRM matrix is concatenated correctly so that scoped/transformed node text positions are accurate. + +### Shadings +Axial and radial gradients (`\shade`). RGB, CMYK, and gray colour models. Rendered natively via MuPDF's `fz_fill_shade`. FunctionType 3 stitching functions are evaluated analytically. Shadings with alpha < 1 use a transparency group. + +### Transparency +`fill opacity` and `draw opacity`. Transparency groups (`transparency group` in `\begin{scope}`) render correctly for overlapping semi-transparent objects. + +### Decorations +`zigzag`, `snake`, `brace`, `text along path`. Markings with arrow tips at arbitrary positions. + +### Node Positioning +`above`, `below`, `left`, `right` with offsets. Named anchors (`.north`, `.south`, `.east`, `.west`, `.north east`, etc.). `fit` library. + +### Loops & Maths +`\foreach` with `evaluate`, `count`, `remember`. `calc` library: `($(A)!0.5!(B)$)`, `($(A)+(1,2)$)`, `($(A)!(C)!(B)$)`. + +### Matrix (Manual) +A grid of named nodes replaces the `\matrix` command (which requires `&` that conflicts with TeXpresso's frontend). Equivalent output. + +### Patterns (Partial) +**Colour extraction works** — pattern-filled shapes show the correct colour. **Texture tiling is not implemented.** See *Known Issues* below. + +## Architecture + +### Two Special Formats +PGF emits content via two drivers, both are handled: + +1. **dvips** — `ps:` and `ps::` DVI specials. Parsed by `ps_code()` in `dvi_special.c`. Tokens are interpreted by a simple stack machine. + +2. **dvipdfmx** — `pdf:` DVI specials. Content streams (`pdf:code` / `pdf:literal`) are parsed by `pdf_code()` which uses a vstack-based PDF operator interpreter. Graphics-state specials (`pdf:btrans`/`pdf:etrans`, `pdf:bc`/`pdf:ec`, `pdf:q`/`pdf:Q`) are handled directly. + +### PS Function Table +PGF defines ~96 internal functions via `!` specials. TeXpresso stores the 16 most recent in `ps_funcs[]` (PS_FUNC_MAX=16). This limit is a careful trade-off: expanding it lets internal functions leak into the token-dispatch fallback path and corrupt rendering state. + +### Shading Pipeline +`try_parse_ps_shading()` intercepts `<<` dictionaries inside `ps:` specials. When it detects a valid shading dictionary (`/ShadingType`, `/Coords`, `/Function`), it parses the parameters and calls `render_axial_shade()` or `render_radial_shade()`, which use MuPDF's `fz_fill_shade` to draw the gradient directly onto the device. + +### Colour Management +PGF sets colours by defining a function (`/pgffc{...}def`) and later looking it up (`ps_lookup_func("pgffc")`). TeXpresso executes colour-function bodies at definition time so that inline `setrgbcolor`/`setgray`/`setcmykcolor` commands update `st->gs.colors.fill` and `st->gs.colors.line` immediately. This avoids a round-trip through the PS interpreter when the fill/stroke path is drawn. + +## Known Issues + +### Pattern Textures (Phase 13) +Pattern fills show the correct colour but no tiled texture. PGF emits patterns as PostScript `makepattern`/`setcolor` operators (dvips driver) or as `pdf:stream`/`pdf:put` specials (dvipdfmx driver). Neither path is handled by the current PS interpreter or PDF special handler. The dvips path requires implementing PatternType 1 tiling with per-tile PaintProc execution and clip management; the dvipdfmx path requires adding `stream`/`obj`/`put` rules in the PDF special dispatcher and modifying the `scn` operator to accept 4-operand pattern colours. Both approaches were prototyped but introduced crashes or incorrect output. + +### Single-Page Hang with Direct XDV Rendering +When invoked as `texpresso file.xdv`, only the first page renders before the process hangs. The VSCode extension pipeline does not have this issue. Likely a teardown-path bug in the standalone DVI engine, not related to TikZ support. diff --git a/doc/tikz1.png b/doc/tikz1.png new file mode 100644 index 00000000..541a7bae Binary files /dev/null and b/doc/tikz1.png differ diff --git a/doc/tikz3.png b/doc/tikz3.png new file mode 100644 index 00000000..b3ba44d1 Binary files /dev/null and b/doc/tikz3.png differ diff --git a/doc/tikz5.png b/doc/tikz5.png new file mode 100644 index 00000000..f0d63848 Binary files /dev/null and b/doc/tikz5.png differ diff --git a/src/common/tectonic_provider.c b/src/common/tectonic_provider.c index 30811e58..886bc253 100644 --- a/src/common/tectonic_provider.c +++ b/src/common/tectonic_provider.c @@ -6,108 +6,384 @@ #include #include -int *entries; -int entries_pow = 1; +#define READ_BUF_SIZE 4096 +#define READ_HASH_SIZE 128 +#define TECTONIC_PATH_MAX 4096 +#define TECTONIC_CMD_MAX 1024 + +/* -------------------------------------------------------------------------- */ +/* Global State (Dynamic buffers for index, dirs, entries) */ +/* -------------------------------------------------------------------------- */ -char *names = NULL; -size_t names_len = 0, names_cap = 1; +int *entries = NULL; +int entries_pow = 1; +int entries_cap = 1; -static void append_buffer(char *buffer, int len) +struct dynbuf { - size_t new_len = names_len + len; - if (new_len > names_cap) - { - while (new_len > names_cap) - names_cap *= 2; - names = realloc(names, names_cap); - if (names == NULL) - do_abortf("Cannot allocate buffer to read Tectonic bundle"); - } + char *data; + size_t len; + size_t cap; +} tt_index = {0, .cap = 1}, tt_dirs = {.cap = 1}; + +bool tt_index_skip = false; +bool tt_is_v15 = false; + +/* Static buffers for paths, commands, and cache dir */ +static char g_path_buf[TECTONIC_PATH_MAX]; +static char tectonic_cache_dir[TECTONIC_PATH_MAX] = {0}; +static bool tectonic_cache_init = false; - memmove(names + names_len, buffer, len); - names_len += len; +static void dynbuf_ensure_capacity(struct dynbuf *buf, size_t cap) +{ + if (cap < buf->cap) + return; + while (cap >= buf->cap) + buf->cap *= 2; + char *tmp = realloc(buf->data, buf->cap); + if (!tmp) + do_abortf("tectonic provider: cannot allocate dynamic buffer"); + buf->data = tmp; } -static unsigned long sdbm_hash(const void *p) +/* -------------------------------------------------------------------------- */ +/* Hash Table Utilities */ +/* -------------------------------------------------------------------------- */ + +static unsigned long sdbm_hash(const char *p) { unsigned long hash = 0; int c; - - for (const unsigned char *str = p; (c = *str); str++) + for (const unsigned char *str = (const unsigned char *)p; (c = *str); str++) hash = c + (hash << 6) + (hash << 16) - hash; - return hash * 2654435761; } -int lookup_entry_index(const char *name) +static int lookup_entry_index(const char *name) { unsigned long hash = sdbm_hash(name); int mask = (1 << entries_pow) - 1; int index = hash & mask; - while (1) { int offset = entries[index]; if (offset == -1) return index; - if (strcmp(name, names + offset) == 0) + if (strcmp(name, tt_index.data + offset) == 0) return index; index = (index + 1) & mask; } } -static bool check_cache_validity(void) +static void build_hash_table(void) { - const char *dir = cache_path("tectonic", "SHA256SUM"); + int count = 0; + for (size_t i = 0; i < tt_index.len; i++) + if (tt_index.data[i] == '\0') + count++; - // Give up if there is no cache directory - if (!dir) - return 0; + int min_cap = count * 4 / 3; + while ((1 << entries_pow) <= min_cap) + entries_pow++; + entries_cap = 1 << entries_pow; + + entries = realloc(entries, entries_cap * sizeof(int)); + if (!entries) + do_abortf("Cannot allocate table for indexing Tectonic bundle."); + memset(entries, -1, entries_cap * sizeof(int)); + + for (size_t i = 0, j = 0; j < tt_index.len; j++) + { + if (tt_index.data[j] != '\0') + continue; + if (i != j) + { + int index = lookup_entry_index(tt_index.data + i); + if (entries[index] == -1) + entries[index] = (int)i; + else + fprintf(stderr, "tectonic bundle: duplicate entry (%s)\n", + tt_index.data + i); + } + i = j + 1; + } + + fprintf(stderr, "tectonic bundle: indexing succeeded (%d entries)\n", count); +} + +/* -------------------------------------------------------------------------- */ +/* Path Building (Macro + Static Buffer Mutation) */ +/* -------------------------------------------------------------------------- */ + +static const char *tectonic_user_cache_path_impl(const char *suffixes[]) +{ + if (!tectonic_cache_init) + { + tectonic_cache_init = true; + FILE *p = popen("tectonic -X show user-cache-dir", "r"); + if (!p) + { + fprintf(stderr, "tectonic: not found\n"); + return NULL; + } + size_t n = fread(tectonic_cache_dir, 1, sizeof(tectonic_cache_dir) - 1, p); + pclose(p); + if (n > 0 && tectonic_cache_dir[n - 1] == '\n') + { + n--; + tectonic_cache_dir[n] = '\0'; + fprintf(stderr, "tectonic: cache directory is %s\n", tectonic_cache_dir); + } + else + { + fprintf(stderr, "tectonic provider: malformed cache path\n"); + return NULL; + } + } + + char *ptr = stpcpy(g_path_buf, tectonic_cache_dir); + for (const char **s = suffixes; *s; s++) + ptr = stpcpy(ptr, *s); + return g_path_buf; +} + +#define tectonic_user_cache_path(...) \ + tectonic_user_cache_path_impl( \ + (const char *[]){__VA_ARGS__ __VA_OPT__(, ) NULL}) + +/* -------------------------------------------------------------------------- */ +/* Shell Quoting & Command Building (Allocation-Free) */ +/* -------------------------------------------------------------------------- */ + +static void shell_quote(char *buf, const char *name) +{ + *buf++ = '\''; + while (*name) + { + if (*name == '\'') + { + *buf++ = '\\'; + *buf++ = '\''; + *buf++ = '\''; + } + else + { + *buf++ = *name; + } + name++; + } + *buf++ = '\''; + *buf = '\0'; +} + +static const char *build_cat_cmd(const char *name, bool to_stdout) +{ + static char buf[TECTONIC_CMD_MAX]; + char *p = stpcpy(buf, to_stdout ? "tectonic -X bundle cat " + : ">/dev/null tectonic -X bundle cat "); + shell_quote(p, name); + return buf; +} + +/* -------------------------------------------------------------------------- */ +/* Tectonic 0.16 Support */ +/* -------------------------------------------------------------------------- */ + +static void tt16_add_dir(const char *name) +{ + size_t name_len = strlen(name) + 1; + size_t new_len = tt_dirs.len + name_len; + dynbuf_ensure_capacity(&tt_dirs, new_len); + memcpy(tt_dirs.data + tt_dirs.len, name, name_len); + tt_dirs.len = new_len; +} + +static void tt16_add_to_index(const char *buffer, size_t len) +{ + size_t new_cap = tt_index.len + len; + dynbuf_ensure_capacity(&tt_index, new_cap); + for (size_t i = 0; i < len; i++) + { + if (tt_index_skip) + { + if (buffer[i] == '\n') + tt_index_skip = false; + } + else + { + if (buffer[i] == ' ' || buffer[i] == '\n') + { + tt_index_skip = true; + tt_index.data[tt_index.len++] = '\0'; + } + else + { + tt_index.data[tt_index.len++] = buffer[i]; + } + } + } +} + +static void tt16_load_indexes(void) +{ + const char *data_dir = tectonic_user_cache_path("/data/"); + if (!data_dir) + return; + + DIR *dirp = opendir(data_dir); + if (!dirp) + return; + + struct dirent *entry; + while ((entry = readdir(dirp)) != NULL) + { + if (entry->d_type == DT_DIR) + { + tt16_add_dir(entry->d_name); + } + else if (entry->d_type == DT_REG) + { + size_t name_len = strlen(entry->d_name); + if (name_len >= 6 && strcmp(entry->d_name + name_len - 6, ".index") == 0) + { + const char *idx_path = + tectonic_user_cache_path("/data/", entry->d_name); + FILE *f = fopen(idx_path, "r"); + if (f) + { + char buf[READ_BUF_SIZE]; + ssize_t n; + while ((n = fread(buf, 1, READ_BUF_SIZE, f)) > 0) + tt16_add_to_index(buf, n); + fclose(f); + } + else + { + fprintf(stderr, "tectonic provider: cannot read index file %s\n", + idx_path); + } + } + } + } + closedir(dirp); +} + +static bool tt16_list_tectonic_files(void) +{ + tt16_load_indexes(); + if (tt_index.len == 0) + { + fprintf(stderr, + "Cannot find Tectonic bundle(s) manifests. Trying to initialize " + "tectonic now.\n"); + system("tectonic -X bundle cat SHA256SUM >/dev/null"); + tt16_load_indexes(); + } + if (tt_index.len == 0) + return false; + + build_hash_table(); + return true; +} + +static const char *tt16_get_file_path(const char *name) +{ + static char result_path[TECTONIC_PATH_MAX]; + for (const char *dir = tt_dirs.data, *end = dir + tt_dirs.len; dir < end; + dir += strlen(dir) + 1) + { + const char *path = tectonic_user_cache_path("/data/", dir, "/", name); + if (access(path, R_OK) != 0) + continue; + strcpy(result_path, path); + return result_path; + } + + fprintf(stderr, + "tectonic provider: %s missing, trying to fetch with tectonic\n", + name); - FILE *f; + int retcode = system(build_cat_cmd(name, false)); + if (retcode != 0) + fprintf(stderr, "tectonic provider: command returned code %d\n", retcode); - // Check version of our cache - f = fopen(dir, "rb"); - // No SHA256SUM: cache is either uninitialized or damaged + for (const char *dir = tt_dirs.data, *end = dir + tt_dirs.len; dir < end; + dir += strlen(dir) + 1) + { + const char *path = tectonic_user_cache_path("/data/", dir, "/", name); + if (access(path, R_OK) != 0) + continue; + strcpy(result_path, path); + fprintf(stderr, "tectonic provider: found %s\n", result_path); + return result_path; + } + + do_abortf("tectonic provider: cannot load %s, skipping\n", name); + return NULL; +} + +/* -------------------------------------------------------------------------- */ +/* Tectonic 0.15 Support */ +/* -------------------------------------------------------------------------- */ + +static void tt15_append_buffer(const char *buffer, size_t len) +{ + size_t new_len = tt_index.len + len; + dynbuf_ensure_capacity(&tt_index, new_len); + char *data = tt_index.data + tt_index.len; + for (size_t i = 0; i < len; i++) + { + if (buffer[i] == '\n') + data[i] = '\0'; + else + data[i] = buffer[i]; + } + tt_index.len += len; +} + +static bool tt15_check_cache_validity(void) +{ + const char *dir = cache_path("tectonic", "SHA256SUM"); + if (!dir) + return false; + + FILE *f = fopen(dir, "rb"); if (!f) - return 0; + return false; - char b1[4096]; - int r1 = fread(b1, 1, 4096, f); + char b1[READ_HASH_SIZE]; + size_t r1 = fread(b1, 1, READ_HASH_SIZE, f); fclose(f); - // Compare tectonic SHA256SUM and ours FILE *p = popen("tectonic -X bundle cat SHA256SUM", "r"); - // No tectonic: we cannot check cache :/ if (!p) - return 0; + return false; - char b2[4096]; - int r2 = fread(b2, 1, 4096, p); - b2[r2] = 0; + char b2[READ_HASH_SIZE]; + size_t r2 = fread(b2, 1, READ_HASH_SIZE, p); pclose(p); return (r1 == r2) && (memcmp(b1, b2, r1) == 0); } -static void prepare_cache(void) +static void tt15_prepare_cache(void) { - if (check_cache_validity()) + if (tt15_check_cache_validity()) return; - // Base cache directory const char *path = cache_path("tectonic", NULL); + if (!path) + return; - DIR *dir; - struct dirent *entry; - char filePath[1024]; - - if ((dir = opendir(path)) == NULL) + DIR *dir = opendir(path); + if (!dir) { perror("opendir"); return; } + struct dirent *entry; + char filePath[TECTONIC_PATH_MAX]; while ((entry = readdir(dir)) != NULL) { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) @@ -116,109 +392,40 @@ static void prepare_cache(void) if (unlink(filePath) != 0) perror("unlink"); } - closedir(dir); FILE *f = tectonic_get_file("SHA256SUM"); - if (f) fclose(f); + if (f) + fclose(f); } -static void list_tectonic_files(void) +static bool tt15_list_tectonic_files(void) { - static int loaded = 0; - if (loaded) - return; - loaded = 1; - - // Read the list of all files FILE *f = popen("tectonic -X bundle search", "r"); - if (f == NULL) + if (!f) do_abortf("Failed to execute 'tectonic -X bundle search'"); - char buffer[4096]; - ssize_t read; - while ((read = fread(buffer, 1, 4096, f)) > 0) - { - append_buffer(buffer, read); - } + char buffer[READ_HASH_SIZE]; + ssize_t n; + while ((n = fread(buffer, 1, READ_HASH_SIZE, f)) > 0) + tt15_append_buffer(buffer, n); if (ferror(f)) - { perror("fread/popen"); - } - pclose(f); - // Count number of lines - int count = 0; - for (int i = 0; i < names_len; i++) - if (names[i] == '\n') - count += 1; - - // Allocate hashtable for indexing lines - int min_cap = count * 4 / 3; - while ((1 << entries_pow) <= min_cap) - entries_pow++; - entries = malloc((1 << entries_pow) * sizeof(*entries)); - if (entries == NULL) - do_abortf("Cannot allocate table for indexing Tectonic bundle."); - for (int i = 0; i < (1 << entries_pow); i++) - entries[i] = -1; - - // Populate table - for (int i = 0, j = 0; j < names_len; j++) - { - if (names[j] == '\n') - { - names[j] = '\0'; - int index = lookup_entry_index(names + i); - if (entries[index] == -1) - entries[index] = i; - else - fprintf(stderr, "tectonic bundle: duplicate entry (%s)\n", names + i); - i = j + 1; - } - } - - fprintf(stderr, "tectonic bundle: indexing succeeded (%d entries)\n", count); - prepare_cache(); + build_hash_table(); + tt15_prepare_cache(); + return tt_index.len > 0; } -FILE *tectonic_cat(const char *name) +static FILE *tt15_cat(const char *name) { - if (!tectonic_has_file(name)) - return NULL; - char buffer[1024] = "tectonic -X bundle cat ", *p = buffer; - while (*p) - p++; - - *p++ = '\''; - while (*name) - { - if ((*p++ = *name++) != '\'') - continue; - *p++ = '\\'; - *p++ = '\''; - *p++ = '\''; - } - *p++ = '\''; - *p = '\0'; - - return popen(buffer, "r"); + return popen(build_cat_cmd(name, true), "r"); } -bool tectonic_has_file(const char *name) +static const char *tt15_get_file_path(const char *name) { - list_tectonic_files(); - int index = lookup_entry_index(name); - return (entries[index] != -1); -} - -const char *tectonic_get_file_path(const char *name) -{ - if (!tectonic_has_file(name)) - return NULL; - const char *cached = cache_path("tectonic", name); if (access(cached, R_OK) == 0) return cached; @@ -227,37 +434,67 @@ const char *tectonic_get_file_path(const char *name) if (!f) return NULL; - FILE *p = tectonic_cat(name); + FILE *p = tt15_cat(name); if (!p) + { + fclose(f); return NULL; + } - char buffer[4096]; - int read; - while ((read = fread(buffer, 1, 4096, p)) > 0) + char buffer[READ_BUF_SIZE]; + ssize_t n; + while ((n = fread(buffer, 1, READ_BUF_SIZE, p)) > 0) { - if (fwrite(buffer, 1, read, f) != read) + if (fwrite(buffer, 1, n, f) != (size_t)n) break; } - if (read != 0 || ferror(p) || ferror(f)) + + bool error = (n != 0) || ferror(p) || ferror(f); + pclose(p); + fclose(f); + + if (error) { - pclose(p); - fclose(f); unlink(cached); return NULL; } - pclose(p); - fclose(f); - return cached; } +/* -------------------------------------------------------------------------- */ +/* Version Dispatch & Public API */ +/* -------------------------------------------------------------------------- */ + +static bool list_tectonic_files(void) +{ + static bool loaded = false, result = false; + if (loaded) + return false; + loaded = true; + + if (tt16_list_tectonic_files()) + return (result = true); + tt_is_v15 = true; + if (tt15_list_tectonic_files()) + return (result = true); + + return false; +} + +const char *tectonic_get_file_path(const char *name) +{ + if (!tectonic_has_file(name)) + return NULL; + else if (tt_is_v15) + return tt15_get_file_path(name); + else + return tt16_get_file_path(name); +} + FILE *tectonic_get_file(const char *name) { const char *path = tectonic_get_file_path(name); - if (path) - return fopen(path, "rb"); - else - return NULL; + return path ? fopen(path, "rb") : NULL; } void tectonic_record_version(FILE *fr) @@ -268,12 +505,11 @@ void tectonic_record_version(FILE *fr) fwrite("!", 1, 1, fr); return; } - - char buffer[4096]; - int read; - while ((read = fread(buffer, 1, 4096, fh)) > 0) + char buffer[READ_HASH_SIZE]; + ssize_t n; + while ((n = fread(buffer, 1, READ_HASH_SIZE, fh)) > 0) { - if (fwrite(buffer, 1, read, fr) != read) + if (fwrite(buffer, 1, n, fr) != (size_t)n) break; } fclose(fh); @@ -287,14 +523,14 @@ bool tectonic_check_version(FILE *fr) char c; return (fread(&c, 1, 1, fr) == 1 && c == '!'); } - - char b1[4096], b2[4096]; - int read, valid = 1; - while ((read = fread(b1, 1, 4096, fh)) > 0) + char b1[READ_HASH_SIZE], b2[READ_HASH_SIZE]; + ssize_t n; + bool valid = true; + while ((n = fread(b1, 1, READ_HASH_SIZE, fh)) > 0) { - if ((fread(b2, 1, read, fr) == read) && (memcmp(b1, b2, read) == 0)) + if ((fread(b2, 1, n, fr) == (size_t)n) && (memcmp(b1, b2, n) == 0)) continue; - valid = 0; + valid = false; break; } valid = valid && (feof(fh) && !ferror(fh)); @@ -302,6 +538,13 @@ bool tectonic_check_version(FILE *fr) return valid; } +bool tectonic_has_file(const char *name) +{ + list_tectonic_files(); + int index = lookup_entry_index(name); + return entries[index] != -1; +} + bool tectonic_available(void) { return tectonic_has_file("SHA256SUM"); diff --git a/src/dvi/dvi_context.c b/src/dvi/dvi_context.c index 4045ab1e..71cf22d6 100644 --- a/src/dvi/dvi_context.c +++ b/src/dvi/dvi_context.c @@ -74,11 +74,19 @@ void dvi_context_begin_frame(fz_context *ctx, dvi_context *dc, fz_device *dev) st->gs.ctm.d = -1; st->gs.ctm.e = 72; st->gs.ctm.f = 72; + dc->base_ctm = st->gs.ctm; + st->gs.fill_alpha = 1.0f; + st->gs.stroke_alpha = 1.0f; + st->gs.text.scale = 1.0f; // Tz default: 100% = no horizontal scaling + st->gs.text.font_size = 1.0f; // minimum safe default st->gs_stack.depth = 0; dc->colorstack.depth = 0; for (int i = 0; i < dc->pdfcolorstacks.capacity; i++) dc->pdfcolorstacks.stacks[i].depth = 0; + + // Reset PS function state to prevent cross-page contamination + ps_state_reset(); } void dvi_context_end_frame(fz_context *ctx, dvi_context *dc) diff --git a/src/dvi/dvi_prim.c b/src/dvi/dvi_prim.c index 92548a00..953b3fd2 100644 --- a/src/dvi/dvi_prim.c +++ b/src/dvi/dvi_prim.c @@ -37,19 +37,7 @@ static void output_fill_rect(fz_context *ctx, dvi_context *dc, dvi_state *st, in fz_path *path = fz_new_path(ctx); fz_rectto(ctx, path, x0 * s, - y0 * s, x1 * s, - y1 * s); fz_fill_path(ctx, dc->dev, path, 0, st->gs.ctm, fz_device_rgb(ctx), - st->gs.colors.fill, 1.0, color_params); - fz_drop_path(ctx, path); - } -} - -static void output_debug_rect(fz_context *ctx, dvi_context *dc, dvi_state *st, int32_t x0, int32_t y0, int32_t x1, int32_t y1) -{ - if (ctx && dc->dev) - { - fz_path *path = fz_new_path(ctx); - fz_rectto(ctx, path, x0 * dc->scale, - y0 * dc->scale, x1 * dc->scale, - y1 * dc->scale); - fz_stroke_path(ctx, dc->dev, path, &fz_default_stroke_state, st->gs.ctm, - fz_device_rgb(ctx), st->gs.colors.line, 0.8, color_params); + st->gs.colors.fill, st->gs.fill_alpha, color_params); fz_drop_path(ctx, path); } } @@ -60,8 +48,28 @@ void dvi_context_flush_text(fz_context *ctx, dvi_context *dc, dvi_state *st) { if (!dc->dev) abort(); - fz_fill_text(ctx, dc->dev, dc->text, fz_identity, fz_device_rgb(ctx), - st->gs.colors.fill, 1.0, color_params); + // Respect PDF text render mode (Tr): 0=fill, 1=stroke, 2=fill+stroke, 3=invisible + switch (st->gs.text.render) + { + case 0: + fz_fill_text(ctx, dc->dev, dc->text, fz_identity, fz_device_rgb(ctx), + st->gs.colors.fill, st->gs.fill_alpha, color_params); + break; + case 1: + fz_stroke_text(ctx, dc->dev, dc->text, &fz_default_stroke_state, fz_identity, + fz_device_rgb(ctx), st->gs.colors.line, st->gs.stroke_alpha, color_params); + break; + case 2: + fz_fill_text(ctx, dc->dev, dc->text, fz_identity, fz_device_rgb(ctx), + st->gs.colors.fill, st->gs.fill_alpha, color_params); + break; + case 3: + break; + default: + fz_fill_text(ctx, dc->dev, dc->text, fz_identity, fz_device_rgb(ctx), + st->gs.colors.fill, st->gs.fill_alpha, color_params); + break; + } fz_drop_text(ctx, dc->text); dc->text = NULL; } @@ -81,9 +89,6 @@ static dvi_fontdef *dvi_current_font(fz_context *ctx, dvi_state *st) void dvi_exec_char(fz_context *ctx, dvi_context *dc, dvi_state *st, uint32_t c, bool set) { - int debug = 0; - - // fprintf(stderr, "%s_char: %C = %u\n", set ? "set" : "put", c, c); dvi_fontdef *def = dvi_current_font(ctx, st); if (def->kind != TEX_FONT) @@ -136,8 +141,9 @@ void dvi_exec_char(fz_context *ctx, dvi_context *dc, dvi_state *st, uint32_t c, if (dc->dev) { float s = dc->scale * scale_factor.value; + fz_matrix ctm = dvi_get_ctm(dc, st); fz_show_glyph(ctx, get_text(ctx, dc), font->fz, - fz_pre_scale(dvi_get_ctm(dc, st), s, s), u, c, 0, 0, + fz_pre_scale(ctm, s, s), u, c, 0, 0, FZ_BIDI_LTR, FZ_LANG_UNSET); } } @@ -175,28 +181,6 @@ void dvi_exec_char(fz_context *ctx, dvi_context *dc, dvi_state *st, uint32_t c, { tex_tfm *tfm = font->tfm; fixed_t w = fixed_mul(tex_tfm_char_width(tfm, c), scale_factor); - if (debug) - { - float s = dc->scale * scale_factor.value; - fixed_t h = tex_tfm_char_height(tfm, c); - fixed_t d = tex_tfm_char_depth(tfm, c); - if (debug) - { - h = fixed_mul(h, scale_factor); - d = fixed_mul(d, scale_factor); - fprintf(stderr, "setchar%u h:=%d+%d=%d\n", c, st->registers.h, - w.value, st->registers.h + w.value); - fprintf(stderr, " char: w:%dr, h:%dr, d:%dr\n", w.value, h.value, - d.value); - fprintf(stderr, " box: (%dr, %dr, %dr, %dr)\n", st->registers.h, - st->registers.v - h.value, st->registers.h + w.value, - st->registers.v + d.value); - - output_debug_rect( - ctx, dc, st, st->registers.h, st->registers.v - h.value, - st->registers.h + w.value, st->registers.v + d.value); - } - } if (set) st->registers.h += w.value; } @@ -208,7 +192,13 @@ bool dvi_exec_push(fz_context *ctx, dvi_context *dc, dvi_state *st) dvi_context_flush_text(ctx, dc, st); if (st->registers_stack.depth >= st->registers_stack.limit) return 0; + // Refuse if gs_stack is full to prevent save/restore pairing corruption + // (gs_stack is shared with PS gsave/grestore). + if (st->gs_stack.depth >= st->gs_stack.limit) + return 0; st->registers_stack.base[st->registers_stack.depth] = st->registers; + st->gs_stack.base[st->gs_stack.depth] = st->gs; + st->gs_stack.depth += 1; st->registers_stack.depth += 1; return 1; } @@ -220,6 +210,13 @@ bool dvi_exec_pop(fz_context *ctx, dvi_context *dc, dvi_state *st) return 0; st->registers_stack.depth -= 1; st->registers = st->registers_stack.base[st->registers_stack.depth]; + // Restore graphics state saved in matching push. + if (st->gs_stack.depth > 0) { + int cd0 = st->gs.clip_depth; + st->gs_stack.depth -= 1; + st->gs = st->gs_stack.base[st->gs_stack.depth]; + if (dc->dev) for (int i = st->gs.clip_depth; i < cd0; ++i) fz_pop_clip(ctx, dc->dev); + } return 1; } @@ -325,7 +322,6 @@ void dvi_exec_xdvglyphs(fz_context *ctx, dvi_context *dc, dvi_state *st, fixed_t int char_count, uint16_t *chars, int num_glyphs, fixed_t *dx, fixed_t dy0, fixed_t *dy, uint16_t *glyphs) { - //fprintf(stderr, "dvi_exec_xdvglyphs: width:%d, chars:%d, glyphs:%d\n", width.value, char_count, num_glyphs); dvi_fontdef *def = dvi_current_font(ctx, st); if (def->kind != XDV_FONT) { diff --git a/src/dvi/dvi_special.c b/src/dvi/dvi_special.c index 516e30dc..6b550904 100644 --- a/src/dvi/dvi_special.c +++ b/src/dvi/dvi_special.c @@ -1,4 +1,4 @@ -/* Generated by re2c 3.1 on Wed Apr 15 14:47:23 2026 */ +/* Generated by re2c 3.1 on Wed May 27 16:13:31 2026 */ #line 1 "dvi_special.re2c.c" /* * MIT License @@ -36,27 +36,29 @@ #define device_cs fz_device_rgb #define color_params fz_default_color_params + typedef const char *cursor_t; -#line 65 "dvi_special.re2c.c" +#line 66 "dvi_special.re2c.c" -#line 45 "dvi_special.c" +#line 46 "dvi_special.c" static cursor_t yyt1; static cursor_t yyt2; static cursor_t yyt3; static cursor_t yyt4; static cursor_t yyt5; static cursor_t yyt6; -#line 67 "dvi_special.re2c.c" +#line 68 "dvi_special.re2c.c" static bool unhandled(const char *kind, cursor_t cur, cursor_t lim, int ignored) { - if (0 && !ignored) - fprintf(stderr, "unhandled %s: \"%.*s\"\n", kind, (int)(lim - cur), cur); - return 0; + (void)kind; (void)cur; (void)lim; (void)ignored; + // Don't print warnings for unrecognized specials to keep output clean. + // Return 1 to prevent aborting page rendering. + return 1; } static int pnat(cursor_t ptr, cursor_t lim) @@ -195,43 +197,43 @@ static bool parse_color(dvi_colorstate *state, cursor_t cur, cursor_t lim) do { - -#line 200 "dvi_special.c" + +#line 202 "dvi_special.c" { int yych; static const unsigned char yybm[] = { - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 64, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 64, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, }; mar = cur; yych = cur < lim ? *cur : -1; @@ -246,14 +248,14 @@ static bool parse_color(dvi_colorstate *state, cursor_t cur, cursor_t lim) } } yy1: -#line 242 "dvi_special.re2c.c" +#line 244 "dvi_special.re2c.c" { return unhandled("color", cur, lim, 0); } -#line 252 "dvi_special.c" +#line 254 "dvi_special.c" yy2: ++cur; -#line 216 "dvi_special.re2c.c" +#line 218 "dvi_special.re2c.c" { continue; } -#line 257 "dvi_special.c" +#line 259 "dvi_special.c" yy3: ++cur; yych = cur < lim ? *cur : -1; @@ -411,12 +413,12 @@ static bool parse_color(dvi_colorstate *state, cursor_t cur, cursor_t lim) if (yych <= '9') goto yy23; yy24: f0 = yyt1; -#line 219 "dvi_special.re2c.c" +#line 221 "dvi_special.re2c.c" { color_parse_gray(color, f0, lim); break; } -#line 420 "dvi_special.c" +#line 422 "dvi_special.c" yy25: ++cur; yych = cur < lim ? *cur : -1; @@ -572,12 +574,12 @@ static bool parse_color(dvi_colorstate *state, cursor_t cur, cursor_t lim) f0 = yyt1; f1 = yyt2; f2 = yyt3; -#line 227 "dvi_special.re2c.c" +#line 229 "dvi_special.re2c.c" { color_parse_rgb(color, f0, f1, f2, lim); break; } -#line 581 "dvi_special.c" +#line 583 "dvi_special.c" yy41: ++cur; yych = cur < lim ? *cur : -1; @@ -642,12 +644,12 @@ static bool parse_color(dvi_colorstate *state, cursor_t cur, cursor_t lim) f1 = yyt2; f2 = yyt3; f3 = yyt4; -#line 236 "dvi_special.re2c.c" +#line 238 "dvi_special.re2c.c" { color_parse_cmyk(color, f0, f1, f2, f3, lim); break; } -#line 651 "dvi_special.c" +#line 653 "dvi_special.c" yy49: ++cur; yych = cur < lim ? *cur : -1; @@ -655,7 +657,7 @@ static bool parse_color(dvi_colorstate *state, cursor_t cur, cursor_t lim) if (yych <= '9') goto yy49; goto yy48; } -#line 244 "dvi_special.re2c.c" +#line 246 "dvi_special.re2c.c" } while (0); @@ -672,43 +674,43 @@ static bool parse_pdfcolor(dvi_colorstate *state, cursor_t cur, cursor_t lim) for (;;) { - -#line 677 "dvi_special.c" + +#line 679 "dvi_special.c" { int yych; static const unsigned char yybm[] = { - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 128, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 128, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, }; mar = cur; yych = cur < lim ? *cur : -1; @@ -730,19 +732,19 @@ static bool parse_pdfcolor(dvi_colorstate *state, cursor_t cur, cursor_t lim) } } yy51: -#line 303 "dvi_special.re2c.c" +#line 305 "dvi_special.re2c.c" { return unhandled("pdf color", cur, lim, 0); } -#line 736 "dvi_special.c" +#line 738 "dvi_special.c" yy52: ++cur; -#line 266 "dvi_special.re2c.c" +#line 268 "dvi_special.re2c.c" { continue; } -#line 741 "dvi_special.c" +#line 743 "dvi_special.c" yy53: ++cur; -#line 263 "dvi_special.re2c.c" +#line 265 "dvi_special.re2c.c" { return 1; } -#line 746 "dvi_special.c" +#line 748 "dvi_special.c" yy54: ++cur; yych = cur < lim ? *cur : -1; @@ -814,15 +816,15 @@ static bool parse_pdfcolor(dvi_colorstate *state, cursor_t cur, cursor_t lim) yy61: ++cur; f0 = yyt1; -#line 288 "dvi_special.re2c.c" +#line 290 "dvi_special.re2c.c" { return color_parse_gray(state->line, f0, lim); } -#line 820 "dvi_special.c" +#line 822 "dvi_special.c" yy62: ++cur; f0 = yyt1; -#line 284 "dvi_special.re2c.c" +#line 286 "dvi_special.re2c.c" { return color_parse_gray(state->fill, f0, lim); } -#line 826 "dvi_special.c" +#line 828 "dvi_special.c" yy63: ++cur; yych = cur < lim ? *cur : -1; @@ -948,37 +950,37 @@ static bool parse_pdfcolor(dvi_colorstate *state, cursor_t cur, cursor_t lim) f0 = yyt1; f1 = yyt2; f2 = yyt3; -#line 300 "dvi_special.re2c.c" +#line 302 "dvi_special.re2c.c" { return color_parse_rgb(state->line, f0, f1, f2, lim); } -#line 954 "dvi_special.c" +#line 956 "dvi_special.c" yy76: ++cur; f0 = yyt1; f1 = yyt2; f2 = yyt3; -#line 294 "dvi_special.re2c.c" +#line 296 "dvi_special.re2c.c" { return color_parse_rgb(state->fill, f0, f1, f2, lim); } -#line 962 "dvi_special.c" +#line 964 "dvi_special.c" yy77: ++cur; f0 = yyt1; f1 = yyt2; f2 = yyt3; f3 = yyt4; -#line 280 "dvi_special.re2c.c" +#line 282 "dvi_special.re2c.c" { return color_parse_cmyk(state->line, f0, f1, f2, f3, lim); } -#line 971 "dvi_special.c" +#line 973 "dvi_special.c" yy78: ++cur; f0 = yyt1; f1 = yyt2; f2 = yyt3; f3 = yyt4; -#line 273 "dvi_special.re2c.c" +#line 275 "dvi_special.re2c.c" { return color_parse_cmyk(state->fill, f0, f1, f2, f3, lim); } -#line 980 "dvi_special.c" +#line 982 "dvi_special.c" } -#line 305 "dvi_special.re2c.c" +#line 307 "dvi_special.re2c.c" } } @@ -1177,7 +1179,7 @@ embed_image(fz_context *ctx, dvi_context *dc, dvi_state *st, struct xform_spec * else if (h != h) h = w / ar; ctm = fz_pre_scale(fz_pre_translate(ctm, 0, h), w, -h); - fz_fill_image(ctx, dc->dev, img, ctm, 1.0, color_params); + fz_fill_image(ctx, dc->dev, img, ctm, st->gs.fill_alpha, color_params); } fz_always(ctx) { @@ -1219,43 +1221,43 @@ parse_xform_or_dim(struct xform_spec *xf, cursor_t cur, cursor_t lim) while (cur < lim) { - -#line 1224 "dvi_special.c" + +#line 1226 "dvi_special.c" { int yych; static const unsigned char yybm[] = { - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 64, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 64, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, }; mar = cur; yych = cur < lim ? *cur : -1; @@ -1295,14 +1297,14 @@ parse_xform_or_dim(struct xform_spec *xf, cursor_t cur, cursor_t lim) } } yy80: -#line 657 "dvi_special.re2c.c" +#line 659 "dvi_special.re2c.c" { break; } -#line 1301 "dvi_special.c" +#line 1303 "dvi_special.c" yy81: ++cur; -#line 548 "dvi_special.re2c.c" +#line 550 "dvi_special.re2c.c" { continue; } -#line 1306 "dvi_special.c" +#line 1308 "dvi_special.c" yy82: ++cur; yych = cur < lim ? *cur : -1; @@ -1645,12 +1647,12 @@ parse_xform_or_dim(struct xform_spec *xf, cursor_t cur, cursor_t lim) if (yych <= '9') goto yy142; yy143: f0 = yyt1; -#line 557 "dvi_special.re2c.c" +#line 559 "dvi_special.re2c.c" { xf->clip = pint(f0, lim); continue; } -#line 1654 "dvi_special.c" +#line 1656 "dvi_special.c" yy144: ++cur; yych = cur < lim ? *cur : -1; @@ -1686,12 +1688,12 @@ parse_xform_or_dim(struct xform_spec *xf, cursor_t cur, cursor_t lim) if (yych <= '9') goto yy147; yy148: f0 = yyt1; -#line 611 "dvi_special.re2c.c" +#line 613 "dvi_special.re2c.c" { xf->page = pint(f0, lim); continue; } -#line 1695 "dvi_special.c" +#line 1697 "dvi_special.c" yy149: ++cur; yych = cur < lim ? *cur : -1; @@ -1873,12 +1875,12 @@ parse_xform_or_dim(struct xform_spec *xf, cursor_t cur, cursor_t lim) if (yych <= '9') goto yy164; yy165: f0 = yyt1; -#line 563 "dvi_special.re2c.c" +#line 565 "dvi_special.re2c.c" { sx = sy = pfloat(f0, lim); continue; } -#line 1882 "dvi_special.c" +#line 1884 "dvi_special.c" yy166: ++cur; yych = cur < lim ? *cur : -1; @@ -2093,12 +2095,12 @@ parse_xform_or_dim(struct xform_spec *xf, cursor_t cur, cursor_t lim) if (yych <= '9') goto yy183; yy184: f0 = yyt1; -#line 551 "dvi_special.re2c.c" +#line 553 "dvi_special.re2c.c" { r = pfloat(f0, lim); continue; } -#line 2102 "dvi_special.c" +#line 2104 "dvi_special.c" yy185: ++cur; yych = cur < lim ? *cur : -1; @@ -2160,12 +2162,12 @@ parse_xform_or_dim(struct xform_spec *xf, cursor_t cur, cursor_t lim) if (yych <= '9') goto yy192; yy193: f0 = yyt1; -#line 569 "dvi_special.re2c.c" +#line 571 "dvi_special.re2c.c" { sx = pfloat(f0, lim); continue; } -#line 2169 "dvi_special.c" +#line 2171 "dvi_special.c" yy194: ++cur; yych = cur < lim ? *cur : -1; @@ -2179,12 +2181,12 @@ parse_xform_or_dim(struct xform_spec *xf, cursor_t cur, cursor_t lim) if (yych <= '9') goto yy195; yy196: f0 = yyt1; -#line 575 "dvi_special.re2c.c" +#line 577 "dvi_special.re2c.c" { sy = pfloat(f0, lim); continue; } -#line 2188 "dvi_special.c" +#line 2190 "dvi_special.c" yy197: ++cur; yych = cur < lim ? *cur : -1; @@ -2213,12 +2215,12 @@ parse_xform_or_dim(struct xform_spec *xf, cursor_t cur, cursor_t lim) yy199: ++cur; f0 = yyt1; -#line 593 "dvi_special.re2c.c" +#line 595 "dvi_special.re2c.c" { xf->depth = pdim(f0, lim); continue; } -#line 2222 "dvi_special.c" +#line 2224 "dvi_special.c" yy200: ++cur; yych = cur < lim ? *cur : -1; @@ -2325,12 +2327,12 @@ parse_xform_or_dim(struct xform_spec *xf, cursor_t cur, cursor_t lim) yy214: ++cur; f0 = yyt1; -#line 581 "dvi_special.re2c.c" +#line 583 "dvi_special.re2c.c" { xf->width = pdim(f0, lim); continue; } -#line 2334 "dvi_special.c" +#line 2336 "dvi_special.c" yy215: ++cur; yych = cur < lim ? *cur : -1; @@ -2373,12 +2375,12 @@ parse_xform_or_dim(struct xform_spec *xf, cursor_t cur, cursor_t lim) yy221: ++cur; f0 = yyt1; -#line 587 "dvi_special.re2c.c" +#line 589 "dvi_special.re2c.c" { xf->height = pdim(f0, lim); continue; } -#line 2382 "dvi_special.c" +#line 2384 "dvi_special.c" yy222: ++cur; yych = cur < lim ? *cur : -1; @@ -2547,7 +2549,7 @@ parse_xform_or_dim(struct xform_spec *xf, cursor_t cur, cursor_t lim) f1 = yyt2; f2 = yyt3; f3 = yyt4; -#line 602 "dvi_special.re2c.c" +#line 604 "dvi_special.re2c.c" { xf->bbox.x0 = pfloat(f0, lim); xf->bbox.x1 = pfloat(f1, lim); @@ -2555,7 +2557,7 @@ parse_xform_or_dim(struct xform_spec *xf, cursor_t cur, cursor_t lim) xf->bbox.y1 = pfloat(f3, lim); continue; } -#line 2559 "dvi_special.c" +#line 2561 "dvi_special.c" yy246: ++cur; yych = cur < lim ? *cur : -1; @@ -2654,7 +2656,7 @@ parse_xform_or_dim(struct xform_spec *xf, cursor_t cur, cursor_t lim) yy258: ++cur; f0 = yyt1; -#line 617 "dvi_special.re2c.c" +#line 619 "dvi_special.re2c.c" { int c = f0[0]; switch (c) @@ -2677,7 +2679,7 @@ parse_xform_or_dim(struct xform_spec *xf, cursor_t cur, cursor_t lim) } continue; } -#line 2681 "dvi_special.c" +#line 2683 "dvi_special.c" yy259: ++cur; yych = cur < lim ? *cur : -1; @@ -2762,7 +2764,7 @@ parse_xform_or_dim(struct xform_spec *xf, cursor_t cur, cursor_t lim) f3 = yyt4; f4 = yyt5; f5 = yyt6; -#line 646 "dvi_special.re2c.c" +#line 648 "dvi_special.re2c.c" { xf->ctm.a = pfloat(f0, lim); xf->ctm.b = pfloat(f1, lim); @@ -2772,7 +2774,7 @@ parse_xform_or_dim(struct xform_spec *xf, cursor_t cur, cursor_t lim) xf->ctm.f = pfloat(f5, lim); continue; } -#line 2776 "dvi_special.c" +#line 2778 "dvi_special.c" yy268: ++cur; yych = cur < lim ? *cur : -1; @@ -2780,7 +2782,7 @@ parse_xform_or_dim(struct xform_spec *xf, cursor_t cur, cursor_t lim) if (yych <= '9') goto yy268; goto yy267; } -#line 659 "dvi_special.re2c.c" +#line 661 "dvi_special.re2c.c" } @@ -2856,6 +2858,148 @@ static void get_stroke_state(fz_context *ctx, dvi_state *st, fz_stroke_state *st stst->dash_phase = st->gs.dash_phase; } +// Resolve a font by name for TikZ text rendering. +// Uses a simple cache in dvi_context to avoid reloading fonts. +static fz_font *resolve_special_font(fz_context *ctx, dvi_context *dc, dvi_state *st, const char *name) +{ + if (!name || !*name) + return NULL; + + // Skip leading '/' + const char *fname = (*name == '/') ? name + 1 : name; + + // Check cache + for (int i = 0; i < dc->font_cache_count; i++) + { + if (strcmp(dc->font_cache[i].name, fname) == 0) + return dc->font_cache[i].font; + } + + // Try MuPDF built-in font + fz_font *font = NULL; + fz_try(ctx) + { + font = fz_new_font_from_file(ctx, NULL, fname, 0, 0); + } + fz_catch(ctx) + { + font = NULL; + } + + // Fallback: try current DVI font + if (!font) + { + dvi_fontdef *def = dvi_fonttable_get(ctx, st->fonts, st->f); + if (def && def->kind == TEX_FONT && def->tex_font.font && def->tex_font.font->fz) + font = def->tex_font.font->fz; + } + + // Cache it + if (font && dc->font_cache_count < DVI_FONT_CACHE_SIZE) + { + size_t len = strlen(fname); + if (len > 63) len = 63; + memcpy(dc->font_cache[dc->font_cache_count].name, fname, len); + dc->font_cache[dc->font_cache_count].name[len] = 0; + dc->font_cache[dc->font_cache_count].font = font; + dc->font_cache_count++; + } + + return font; +} + +// Decode a single UTF-8 code point from str at offset *pi. +// Returns the Unicode code point and advances *pi past the consumed bytes. +// Returns 0 and advances by 1 byte on invalid sequence. +static int decode_utf8(const char *str, size_t len, size_t *pi) +{ + size_t i = *pi; + unsigned char c = (unsigned char)str[i]; + int cp; + int extra; + + if (c < 0x80) { + *pi = i + 1; + return c; + } + if (c < 0xC2) goto invalid; + if (c < 0xE0) { cp = c & 0x1F; extra = 1; } + else if (c < 0xF0) { cp = c & 0x0F; extra = 2; } + else if (c < 0xF8) { cp = c & 0x07; extra = 3; } + else goto invalid; + + *pi = i + 1 + extra; + if (i + extra >= len) return 0; + for (int j = 0; j < extra; j++) { + unsigned char nb = (unsigned char)str[i + 1 + j]; + if ((nb & 0xC0) != 0x80) return 0; + cp = (cp << 6) | (nb & 0x3F); + } + return cp; + +invalid: + *pi = i + 1; + return 0; +} + +// Show a text string for Tj operator, advancing text matrix. +static void show_special_text(fz_context *ctx, dvi_context *dc, dvi_state *st, + const char *str, size_t len, fz_font *font) +{ + if (!font || !str || !len || !dc->dev) + return; + + if (!dc->text) + dc->text = fz_new_text(ctx); + fz_text *text = dc->text; + fz_matrix ctm = dvi_get_ctm(dc, st); + float fs = st->gs.text.font_size; + float hs = st->gs.text.scale; + + size_t i = 0; + while (i < len) + { + int cp = decode_utf8(str, len, &i); + if (cp == 0) + continue; + int glyph = fz_encode_character(ctx, font, cp); + if (glyph == 0) + { + // For missing glyphs, advance Tm by a default space to avoid overlap + float tx = fs * 0.25f * hs; + st->gs.text.Tm = fz_pre_translate(st->gs.text.Tm, tx, 0); + continue; + } + float adv = fz_advance_glyph(ctx, font, glyph, 0); + // TRM = translate(0,rise) × CTM × Tm × Tfs + fz_matrix trm = fz_pre_scale(fz_identity, fs * hs, fs); // Tfs + trm = fz_concat(trm, st->gs.text.Tm); // Tfs × Tm + trm = fz_concat(trm, ctm); // (Tfs × Tm) × CTM + trm = fz_pre_translate(trm, 0, st->gs.text.rise); // apply text rise + fz_show_glyph(ctx, text, font, trm, glyph, cp, 0, 0, FZ_BIDI_LTR, FZ_LANG_UNSET); + // Advance text matrix; word_space only applies to space chars (U+0020) + float tx = (adv * fs + st->gs.text.char_space) * hs; + if (cp == 32) tx += st->gs.text.word_space; + st->gs.text.Tm = fz_pre_translate(st->gs.text.Tm, tx, 0); + } +} + + +// Forward declarations and globals for pattern support (defined later in this file) +static int pdf_pat_lookup(const char *pat_name); +static void render_pdf_pattern_fill(fz_context *ctx, dvi_context *dc, dvi_state *st, int even_odd); + +// Pattern state: must be declared before pdf_code which uses them. +// The full pattern table (pdf_pats[]) is defined later; these are only the +// run-time state variables needed by the fill operators in pdf_code. +static int in_pattern_tile = 0; // recursion guard + +static struct { + int active; // 1 if pattern fill mode is on + int pat_index; // index into pdf_pats[] + float color[3]; // pattern color (for uncolored patterns) +} active_pattern = {0, -1, {0,0,0}}; + static bool pdf_code(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, cursor_t lim) { @@ -2863,7 +3007,6 @@ pdf_code(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, cursor_t fz_var(cur); fz_var(stack); - // fprintf(stderr, "pdf code: %.*s\n", (int)(lim - cur), cur); fz_try(ctx) { enum PDF_OP op; @@ -2916,6 +3059,7 @@ pdf_code(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, cursor_t float c[1]; vstack_get_floats(ctx, stack, c, 1); color_set_gray(st->gs.colors.fill, c[0]); + active_pattern.active = 0; break; } case PDF_OP_RG: @@ -2930,6 +3074,7 @@ pdf_code(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, cursor_t float c[3]; vstack_get_floats(ctx, stack, c, 3); color_set_rgb(st->gs.colors.fill, c[0], c[1], c[2]); + active_pattern.active = 0; break; } case PDF_OP_K: @@ -2944,6 +3089,7 @@ pdf_code(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, cursor_t float c[4]; vstack_get_floats(ctx, stack, c, 4); color_set_cmyk(st->gs.colors.fill, c[0], c[1], c[2], c[3]); + active_pattern.active = 0; break; } @@ -3002,86 +3148,118 @@ pdf_code(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, cursor_t } case PDF_OP_b: - if (dc->dev) - { + { + if (dc->path) fz_closepath(ctx, get_path(ctx, dc)); + if (active_pattern.active && !in_pattern_tile) { + render_pdf_pattern_fill(ctx, dc, st, 0); + } else if (dc->dev) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_path *path = get_path(ctx, dc); + fz_fill_path(ctx, dc->dev, path, 0, ctm, device_cs(ctx), + st->gs.colors.fill, st->gs.fill_alpha, color_params); + } + if (dc->dev && dc->path) { fz_matrix ctm = dvi_get_ctm(dc, st); fz_stroke_state stst; get_stroke_state(ctx, st, &stst); fz_path *path = get_path(ctx, dc); - fz_closepath(ctx, path); - fz_fill_path(ctx, dc->dev, path, 0, ctm, device_cs(ctx), - st->gs.colors.fill, 1.0, color_params); fz_stroke_path(ctx, dc->dev, path, &stst, ctm, device_cs(ctx), - st->gs.colors.line, 1.0, color_params); + st->gs.colors.line, st->gs.stroke_alpha, color_params); } drop_path(ctx, dc); break; + } case PDF_OP_b_star: - if (dc->dev) - { + { + if (dc->path) fz_closepath(ctx, get_path(ctx, dc)); + if (active_pattern.active && !in_pattern_tile) { + render_pdf_pattern_fill(ctx, dc, st, 1); + } else if (dc->dev) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_path *path = get_path(ctx, dc); + fz_fill_path(ctx, dc->dev, path, 1, ctm, device_cs(ctx), + st->gs.colors.fill, st->gs.fill_alpha, color_params); + } + if (dc->dev && dc->path) { fz_matrix ctm = dvi_get_ctm(dc, st); fz_stroke_state stst; get_stroke_state(ctx, st, &stst); fz_path *path = get_path(ctx, dc); - fz_closepath(ctx, path); - fz_fill_path(ctx, dc->dev, path, 1, ctm, device_cs(ctx), - st->gs.colors.fill, 1.0, color_params); fz_stroke_path(ctx, dc->dev, path, &stst, ctm, device_cs(ctx), - st->gs.colors.line, 1.0, color_params); + st->gs.colors.line, st->gs.stroke_alpha, color_params); } drop_path(ctx, dc); break; + } case PDF_OP_B: - if (dc->dev) - { + { + if (active_pattern.active && !in_pattern_tile) { + render_pdf_pattern_fill(ctx, dc, st, 0); + } else if (dc->dev) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_path *path = get_path(ctx, dc); + fz_fill_path(ctx, dc->dev, path, 0, ctm, device_cs(ctx), + st->gs.colors.fill, st->gs.fill_alpha, color_params); + } + if (dc->dev && dc->path) { fz_matrix ctm = dvi_get_ctm(dc, st); fz_stroke_state stst; get_stroke_state(ctx, st, &stst); fz_path *path = get_path(ctx, dc); - fz_fill_path(ctx, dc->dev, path, 0, ctm, device_cs(ctx), - st->gs.colors.fill, 1.0, color_params); fz_stroke_path(ctx, dc->dev, path, &stst, ctm, device_cs(ctx), - st->gs.colors.line, 1.0, color_params); + st->gs.colors.line, st->gs.stroke_alpha, color_params); } drop_path(ctx, dc); break; + } case PDF_OP_B_star: - if (dc->dev) - { + { + if (active_pattern.active && !in_pattern_tile) { + render_pdf_pattern_fill(ctx, dc, st, 1); + } else if (dc->dev) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_path *path = get_path(ctx, dc); + fz_fill_path(ctx, dc->dev, path, 1, ctm, device_cs(ctx), + st->gs.colors.fill, st->gs.fill_alpha, color_params); + } + if (dc->dev && dc->path) { fz_matrix ctm = dvi_get_ctm(dc, st); fz_stroke_state stst; get_stroke_state(ctx, st, &stst); fz_path *path = get_path(ctx, dc); - fz_fill_path(ctx, dc->dev, path, 1, ctm, device_cs(ctx), - st->gs.colors.fill, 1.0, color_params); fz_stroke_path(ctx, dc->dev, path, &stst, ctm, device_cs(ctx), - st->gs.colors.fill, 1.0, color_params); + st->gs.colors.line, st->gs.stroke_alpha, color_params); } drop_path(ctx, dc); break; + } case PDF_OP_f: case PDF_OP_F: - if (dc->dev) - { + if (active_pattern.active && !in_pattern_tile) { + render_pdf_pattern_fill(ctx, dc, st, 0); + active_pattern.active = 0; // consumed + } else if (dc->dev) { fz_matrix ctm = dvi_get_ctm(dc, st); fz_path *path = get_path(ctx, dc); fz_fill_path(ctx, dc->dev, path, 0, ctm, device_cs(ctx), - st->gs.colors.fill, 1.0, color_params); + st->gs.colors.fill, st->gs.fill_alpha, color_params); } drop_path(ctx, dc); break; case PDF_OP_f_star: - if (dc->dev) - { + if (active_pattern.active && !in_pattern_tile) { + render_pdf_pattern_fill(ctx, dc, st, 1); + active_pattern.active = 0; // consumed + } else if (dc->dev) { fz_matrix ctm = dvi_get_ctm(dc, st); fz_path *path = get_path(ctx, dc); fz_fill_path(ctx, dc->dev, path, 1, ctm, device_cs(ctx), - st->gs.colors.fill, 1.0, color_params); + st->gs.colors.fill, st->gs.fill_alpha, color_params); } drop_path(ctx, dc); break; @@ -3094,7 +3272,7 @@ pdf_code(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, cursor_t get_stroke_state(ctx, st, &stst); fz_path *path = get_path(ctx, dc); fz_stroke_path(ctx, dc->dev, path, &stst, ctm, device_cs(ctx), - st->gs.colors.line, 1.0, color_params); + st->gs.colors.line, st->gs.stroke_alpha, color_params); } drop_path(ctx, dc); break; @@ -3108,7 +3286,7 @@ pdf_code(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, cursor_t fz_path *path = get_path(ctx, dc); fz_closepath(ctx, path); fz_stroke_path(ctx, dc->dev, path, &stst, ctm, device_cs(ctx), - st->gs.colors.line, 1.0, color_params); + st->gs.colors.line, st->gs.stroke_alpha, color_params); } drop_path(ctx, dc); break; @@ -3159,77 +3337,419 @@ pdf_code(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, cursor_t val v[2]; vstack_get_arguments(ctx, stack, v, 2); st->gs.dash_len = val_array_length(ctx, stack, v[0]); - if (st->gs.dash_len > 4) st->gs.dash_len = 4; + if (st->gs.dash_len > 32) st->gs.dash_len = 32; for (int i = 0; i < st->gs.dash_len; ++i) st->gs.dash[i] = val_number(ctx, val_array_get(ctx, stack, v[0], i)); st->gs.dash_phase = val_number(ctx, v[1]); break; } - case PDF_OP_ri: - case PDF_OP_i: - case PDF_OP_gs: case PDF_OP_v: + { + fz_path *path = get_path(ctx, dc); + float c[4]; + fz_point pt = fz_currentpoint(ctx, path); + vstack_get_floats(ctx, stack, c, 4); + fz_curveto(ctx, path, pt.x, pt.y, c[0], c[1], c[2], c[3]); + break; + } case PDF_OP_y: + { + fz_path *path = get_path(ctx, dc); + float c[4]; + vstack_get_floats(ctx, stack, c, 4); + fz_curveto(ctx, path, c[0], c[1], c[2], c[3], c[2], c[3]); + break; + } + // -- Text operators (Phase 1) -- case PDF_OP_BT: + { + // PDF defaults for text state (Table 5.3 in PDF 1.7 spec) + st->gs.text.Tm = fz_identity; + st->gs.text.Tlm = fz_identity; + st->gs.text.char_space = 0; // Tc + st->gs.text.word_space = 0; // Tw + st->gs.text.scale = 1.0f; // Tz (100% = no scaling) + st->gs.text.leading = 0; // TL + st->gs.text.render = 0; // Tr (fill) + st->gs.text.rise = 0; // Ts + st->gs.text.in_text = 1; + break; + } case PDF_OP_ET: + { + st->gs.text.in_text = 0; + dvi_context_flush_text(ctx, dc, st); + break; + } case PDF_OP_Tc: + { + float c[1]; + vstack_get_floats(ctx, stack, c, 1); + st->gs.text.char_space = c[0]; + break; + } case PDF_OP_Tw: + { + float c[1]; + vstack_get_floats(ctx, stack, c, 1); + st->gs.text.word_space = c[0]; + break; + } case PDF_OP_Tz: + { + float c[1]; + vstack_get_floats(ctx, stack, c, 1); + st->gs.text.scale = c[0] / 100.0f; + break; + } case PDF_OP_TL: + { + float c[1]; + vstack_get_floats(ctx, stack, c, 1); + st->gs.text.leading = c[0]; + break; + } case PDF_OP_Tf: + { + val v[2]; + vstack_get_arguments(ctx, stack, v, 2); + const char *name = val_as_name(ctx, stack, v[0]); + float size = val_number(ctx, v[1]); + st->gs.text.font_size = size; + if (name) + { + size_t len = strlen(name); + if (len > 63) len = 63; + memcpy(st->gs.text.font_name, name, len); + st->gs.text.font_name[len] = 0; + } + break; + } case PDF_OP_Tr: + { + float c[1]; + vstack_get_floats(ctx, stack, c, 1); + st->gs.text.render = (int)c[0]; + break; + } case PDF_OP_Ts: + { + float c[1]; + vstack_get_floats(ctx, stack, c, 1); + st->gs.text.rise = c[0]; + break; + } case PDF_OP_Td: + { + float c[2]; + vstack_get_floats(ctx, stack, c, 2); + fz_matrix delta = fz_translate(c[0], c[1]); + st->gs.text.Tlm = fz_concat(delta, st->gs.text.Tlm); + st->gs.text.Tm = st->gs.text.Tlm; + break; + } case PDF_OP_TD: + { + float c[2]; + vstack_get_floats(ctx, stack, c, 2); + st->gs.text.leading = -c[1]; + fz_matrix delta = fz_translate(c[0], c[1]); + st->gs.text.Tlm = fz_concat(delta, st->gs.text.Tlm); + st->gs.text.Tm = st->gs.text.Tlm; + break; + } case PDF_OP_Tm: + { + float fmat[6]; + vstack_get_floats(ctx, stack, fmat, 6); + st->gs.text.Tlm.a = fmat[0]; st->gs.text.Tlm.b = fmat[1]; + st->gs.text.Tlm.c = fmat[2]; st->gs.text.Tlm.d = fmat[3]; + st->gs.text.Tlm.e = fmat[4]; st->gs.text.Tlm.f = fmat[5]; + st->gs.text.Tm = st->gs.text.Tlm; + break; + } case PDF_OP_T_star: + { + fz_matrix delta = fz_translate(0, -st->gs.text.leading); + st->gs.text.Tlm = fz_concat(delta, st->gs.text.Tlm); + st->gs.text.Tm = st->gs.text.Tlm; + break; + } case PDF_OP_Tj: + { + val v[1]; + vstack_get_arguments(ctx, stack, v, 1); + const char *str = val_as_string(ctx, stack, v[0]); + size_t slen = val_string_length(ctx, stack, v[0]); + fz_font *font = resolve_special_font(ctx, dc, st, st->gs.text.font_name); + show_special_text(ctx, dc, st, str, slen, font); + break; + } case PDF_OP_TJ: + { + val v[1]; + vstack_get_arguments(ctx, stack, v, 1); + int alen = val_array_length(ctx, stack, v[0]); + fz_font *font = resolve_special_font(ctx, dc, st, st->gs.text.font_name); + for (int i = 0; i < alen; i++) + { + val elem = val_array_get(ctx, stack, v[0], i); + if (val_is_string(elem)) + { + const char *str = val_string(ctx, stack, elem); + size_t slen = val_string_length(ctx, stack, elem); + show_special_text(ctx, dc, st, str, slen, font); + } + else if (val_is_number(elem)) + { + // Kerning adjustment: move text matrix by -kern/1000 * font_size + float kern = val_number(ctx, elem); + float tx = -kern / 1000.0f * st->gs.text.font_size * st->gs.text.scale; + st->gs.text.Tm = fz_pre_translate(st->gs.text.Tm, tx, 0); + } + } + break; + } case PDF_OP_squote: + { + // ' = T* + Tj + fz_matrix delta = fz_translate(0, -st->gs.text.leading); + st->gs.text.Tlm = fz_concat(delta, st->gs.text.Tlm); + st->gs.text.Tm = st->gs.text.Tlm; + val v[1]; + vstack_get_arguments(ctx, stack, v, 1); + const char *str = val_as_string(ctx, stack, v[0]); + size_t slen = val_string_length(ctx, stack, v[0]); + fz_font *font = resolve_special_font(ctx, dc, st, st->gs.text.font_name); + show_special_text(ctx, dc, st, str, slen, font); + break; + } case PDF_OP_dquote: - case PDF_OP_d0: - case PDF_OP_d1: - case PDF_OP_CS: - case PDF_OP_cs: - case PDF_OP_SC: - case PDF_OP_sc: - case PDF_OP_SCN: - case PDF_OP_scn: - case PDF_OP_sh: - case PDF_OP_Do: + { + // '' = set Tw/Tc + T* + Tj + // Get all 3 operands at once: aw, ac, string + val v[3]; + vstack_get_arguments(ctx, stack, v, 3); + st->gs.text.word_space = val_number(ctx, v[0]); + st->gs.text.char_space = val_number(ctx, v[1]); + fz_matrix delta = fz_translate(0, -st->gs.text.leading); + st->gs.text.Tlm = fz_concat(delta, st->gs.text.Tlm); + st->gs.text.Tm = st->gs.text.Tlm; + const char *str = val_as_string(ctx, stack, v[2]); + size_t slen = val_string_length(ctx, stack, v[2]); + fz_font *font = resolve_special_font(ctx, dc, st, st->gs.text.font_name); + show_special_text(ctx, dc, st, str, slen, font); + break; + } + + // No-ops: operators without visual effect. + // IMPORTANT: must consume operands from the stack to prevent corruption. case PDF_OP_MP: + { + val v[1]; + vstack_get_arguments(ctx, stack, v, 1); // tag name + break; + } case PDF_OP_DP: + { + val v[2]; + vstack_get_arguments(ctx, stack, v, 2); // tag name + properties + break; + } case PDF_OP_BMC: + { + val v[1]; + vstack_get_arguments(ctx, stack, v, 1); // tag name + break; + } case PDF_OP_BDC: + { + // BDC has 1 or 2 operands (tag + optional property dict) + // Try popping 2; if the second isn't a dict, it's fine + val v[2]; + vstack_get_arguments(ctx, stack, v, 2); // tag name + properties + break; + } case PDF_OP_EMC: + // EMC: 0 operands + break; + case PDF_OP_BX: case PDF_OP_EX: - default: - fprintf(stderr, "pdf unhandled op %s in:\n%.*s\n", pdf_op_name(op), - (int)(cur - cur0), cur0); + // Compatibility operators: 0 operands break; - case PDF_NONE: + case PDF_OP_ri: + { + val v[1]; + vstack_get_arguments(ctx, stack, v, 1); // rendering intent name break; - } - } while (op != PDF_NONE); - } - fz_always(ctx) - { - vstack_free(ctx, stack); - } - fz_catch(ctx) - { - return 0; - } - return 1; -} + } + case PDF_OP_i: + { + float c[1]; + vstack_get_floats(ctx, stack, c, 1); // flatness value + break; + } -static cursor_t parse_pdf_string(char *buf, char *end, cursor_t cur, cursor_t lim) -{ - if (buf >= end) - return cur; + case PDF_OP_d0: + { + float c[2]; + vstack_get_floats(ctx, stack, c, 2); // wx wy + break; + } + case PDF_OP_d1: + { + float c[6]; + vstack_get_floats(ctx, stack, c, 6); // wx wy llx lly urx ury + break; + } + + // Color space operators: TikZ primarily uses direct color operators + // (rg/RG/g/G/k/K) which are already supported. + case PDF_OP_CS: + case PDF_OP_cs: + { + val v[1]; + vstack_get_arguments(ctx, stack, v, 1); // color space name + break; + } + case PDF_OP_SC: + { + float c[3]; + vstack_get_floats(ctx, stack, c, 3); + color_set_rgb(st->gs.colors.line, c[0], c[1], c[2]); + break; + } + case PDF_OP_sc: + { + float c[3]; + vstack_get_floats(ctx, stack, c, 3); + color_set_rgb(st->gs.colors.fill, c[0], c[1], c[2]); + break; + } + case PDF_OP_SCN: + { + // SCN operand count depends on current color space: + // 1 (Gray), 3 (RGB), 4 (CMYK), N+1 (Pattern name + components) + val array = vstack_get_values(ctx, stack); + int n = val_array_length(ctx, stack, array); + if (n >= 1 && val_is_number(val_array_get(ctx, stack, array, 0))) { + if (n == 1) { + color_set_gray(st->gs.colors.line, val_number(ctx, val_array_get(ctx, stack, array, 0))); + } else if (n == 3) { + color_set_rgb(st->gs.colors.line, + val_number(ctx, val_array_get(ctx, stack, array, 0)), + val_number(ctx, val_array_get(ctx, stack, array, 1)), + val_number(ctx, val_array_get(ctx, stack, array, 2))); + } else if (n == 4) { + color_set_cmyk(st->gs.colors.line, + val_number(ctx, val_array_get(ctx, stack, array, 0)), + val_number(ctx, val_array_get(ctx, stack, array, 1)), + val_number(ctx, val_array_get(ctx, stack, array, 2)), + val_number(ctx, val_array_get(ctx, stack, array, 3))); + } + } + break; + } + case PDF_OP_scn: + { + val array = vstack_get_values(ctx, stack); + int n = val_array_length(ctx, stack, array); + // Check if last operand is a pattern name (e.g., /pgfpatN) + if (n >= 1 && val_is_name(val_array_get(ctx, stack, array, n - 1))) { + const char *name = val_as_name(ctx, stack, val_array_get(ctx, stack, array, n - 1)); + if (name && memcmp(name, "pgfpat", 6) == 0) { + // Pattern fill: R G B /pgfpatN scn (uncolored) or /pgfpatN scn (colored) + int pi = pdf_pat_lookup(name); + if (pi >= 0) { + active_pattern.active = 1; + active_pattern.pat_index = pi; + // Extract RGB color for uncolored patterns - validate types + if (n >= 4 && + val_is_number(val_array_get(ctx, stack, array, 0)) && + val_is_number(val_array_get(ctx, stack, array, 1)) && + val_is_number(val_array_get(ctx, stack, array, 2))) { + active_pattern.color[0] = val_number(ctx, val_array_get(ctx, stack, array, 0)); + active_pattern.color[1] = val_number(ctx, val_array_get(ctx, stack, array, 1)); + active_pattern.color[2] = val_number(ctx, val_array_get(ctx, stack, array, 2)); + } else { + // Default to black for colored patterns or invalid color operands + active_pattern.color[0] = 0; + active_pattern.color[1] = 0; + active_pattern.color[2] = 0; + } + } + } + } else if (n >= 1 && val_is_number(val_array_get(ctx, stack, array, 0))) { + // Normal color — clear pattern + active_pattern.active = 0; + if (n == 1) { + color_set_gray(st->gs.colors.fill, val_number(ctx, val_array_get(ctx, stack, array, 0))); + } else if (n == 3) { + color_set_rgb(st->gs.colors.fill, + val_number(ctx, val_array_get(ctx, stack, array, 0)), + val_number(ctx, val_array_get(ctx, stack, array, 1)), + val_number(ctx, val_array_get(ctx, stack, array, 2))); + } else if (n == 4) { + color_set_cmyk(st->gs.colors.fill, + val_number(ctx, val_array_get(ctx, stack, array, 0)), + val_number(ctx, val_array_get(ctx, stack, array, 1)), + val_number(ctx, val_array_get(ctx, stack, array, 2)), + val_number(ctx, val_array_get(ctx, stack, array, 3))); + } + } + break; + } + + // Still pending implementation: consume operands to prevent stack leak + case PDF_OP_gs: + { + val v[1]; + vstack_get_arguments(ctx, stack, v, 1); // ExtGState name + break; + } + case PDF_OP_sh: + { + val v[1]; + vstack_get_arguments(ctx, stack, v, 1); // shading name + break; + } + case PDF_OP_Do: + { + val v[1]; + vstack_get_arguments(ctx, stack, v, 1); // XObject name + break; + } + default: + fprintf(stderr, "pdf unhandled op %s in:\n%.*s\n", pdf_op_name(op), + (int)(cur - cur0), cur0); + break; + + case PDF_NONE: + break; + } + } while (op != PDF_NONE); + } + fz_always(ctx) + { + vstack_free(ctx, stack); + } + fz_catch(ctx) + { + // Don't abort page rendering on unknown PDF operators + fprintf(stderr, "unhandled pdf content operator near: \"%.*s\"\n", + (int)(lim - cur), cur); + return 1; + } + return 1; +} + +static cursor_t parse_pdf_string(char *buf, char *end, cursor_t cur, cursor_t lim) +{ + if (buf >= end) + return cur; int nesting = 1; end -= 1; @@ -3260,306 +3780,2141 @@ static cursor_t parse_pdf_string(char *buf, char *end, cursor_t cur, cursor_t li return cur; } +#define MAX_SUBFUNC 8 + +typedef struct { + float c0[3], c1[3]; +} shade_subfunc; + +// Evaluate a stitching function at parameter t (0..1). +// nsub sub-functions partition the domain via nsub-1 bounds. +// Each sub-function i has C0[i], C1[i] and does linear interpolation +// across its sub-domain. +static void shade_eval_color(float out[3], int nsub, const shade_subfunc *sf, + const float *bounds, float t) +{ + if (t < 0) t = 0; if (t > 1) t = 1; + if (nsub <= 1) { + float s = t; + out[0] = sf[0].c0[0] + s * (sf[0].c1[0] - sf[0].c0[0]); + out[1] = sf[0].c0[1] + s * (sf[0].c1[1] - sf[0].c0[1]); + out[2] = sf[0].c0[2] + s * (sf[0].c1[2] - sf[0].c0[2]); + return; + } + // Find which sub-function covers t + int k = nsub - 1; // default: last segment + for (int i = 0; i < nsub - 1; i++) { + if (t < bounds[i]) { k = i; break; } + } + // Map t to sub-function's local [0,1] + float lo = (k == 0) ? 0.0f : bounds[k-1]; + float hi = (k == nsub - 1) ? 1.0f : bounds[k]; + float s = (t - lo) / (hi - lo); + out[0] = sf[k].c0[0] + s * (sf[k].c1[0] - sf[k].c0[0]); + out[1] = sf[k].c0[1] + s * (sf[k].c1[1] - sf[k].c0[1]); + out[2] = sf[k].c0[2] + s * (sf[k].c1[2] - sf[k].c0[2]); +} + +static void render_axial_shade_full(fz_context *ctx, dvi_context *dc, dvi_state *st, + float x0, float y0, float x1, float y1, + int nsub, const shade_subfunc *sf, const float *bounds, + fz_path *clip_path); +static void render_radial_shade(fz_context *ctx, dvi_context *dc, dvi_state *st, + float cx, float cy, float r0, float r1, + const shade_subfunc *sf, int nsub, const float *bounds, int nbounds); +static void render_axial_shade(fz_context *ctx, dvi_context *dc, dvi_state *st, + float x0, float y0, float x1, float y1, float c0[3], float c1[3], fz_path *clip_path); + +// Parse a PostScript shading pattern dictionary from raw PS content. +// Scans for /Coords [x0 y0 x1 y1], /C0 [r g b], /C1 [r g b], +// and /Bounds for stitching functions. +// Returns true if a shading was found and rendered natively. +static bool try_parse_ps_shading(fz_context *ctx, dvi_context *dc, dvi_state *st, + const char *p, const char *end) +{ + // Scan for /Coords [x0 y0 x1 y1] (axial, 4 vals) or + // /Coords [x0 y0 r0 x1 y1 r1] (radial, 6 vals) + float cx0=0, cy0=0, cx1=0, cy1=0, rad_r0=0, rad_r1=0; + int ncoords = 0; + bool has_coords = false; + { + const char *s = p; + while (s + 8 < end) { + if (memcmp(s, "/Coords", 7) == 0 && (s[7] == ' ' || s[7] == '\n' || s[7] == '[')) { + s += 7; + while (s < end && (*s == ' ' || *s == '\n')) s++; + if (s < end && *s == '[') { s++; + char tmp[64]; int ti; + float vals[6]; int vi = 0; + while (vi < 6 && s < end) { + while (s < end && (*s == ' ' || *s == '\n')) s++; + if (s >= end || *s == ']') break; + const char *ns = s; + while (ns < end && *ns != ' ' && *ns != '\n' && *ns != ']') ns++; + ti = ns - s; if (ti > 63) ti = 63; + memcpy(tmp, s, ti); tmp[ti] = 0; + vals[vi++] = strtof(tmp, NULL); + s = ns; + } + ncoords = vi; + if (vi >= 4) { + cx0=vals[0]; cy0=vals[1]; cx1=vals[2]; cy1=vals[3]; has_coords = true; + } + if (vi >= 6) { + rad_r0 = vals[2]; + rad_r1 = vals[5]; + } + } + break; + } + s++; + } + } + if (!has_coords) return false; + + // Collect sub-function C0/C1 values in order. + // Also parse /Bounds array for stitching function domain partition. + shade_subfunc subs[MAX_SUBFUNC]; + int nsub = 0; + float bounds[MAX_SUBFUNC - 1]; + int nbounds = 0; + { + const char *s = p; + while (s + 4 < end) { + bool is_c0 = (memcmp(s, "/C0", 3) == 0 && (s[3] == ' ' || s[3] == '\n' || s[3] == '[')); + bool is_c1 = (memcmp(s, "/C1", 3) == 0 && (s[3] == ' ' || s[3] == '\n' || s[3] == '[')); + if (is_c0 || is_c1) { + s += 3; + while (s < end && (*s == ' ' || *s == '\n')) s++; + if (s < end && *s == '[') { s++; + float vals[3]; int vi = 0; + while (vi < 3 && s < end) { + while (s < end && (*s == ' ' || *s == '\n')) s++; + if (s >= end || *s == ']') break; + const char *ns = s; + while (ns < end && *ns != ' ' && *ns != '\n' && *ns != ']') ns++; + int ti = ns - s; if (ti > 63) ti = 63; + char tmp[64]; memcpy(tmp, s, ti); tmp[ti] = 0; + vals[vi++] = strtof(tmp, NULL); + s = ns; + } + if (vi >= 3) { + if (is_c0) { + if (nsub < MAX_SUBFUNC) { + subs[nsub].c0[0]=vals[0]; subs[nsub].c0[1]=vals[1]; subs[nsub].c0[2]=vals[2]; + subs[nsub].c1[0]=vals[0]; subs[nsub].c1[1]=vals[1]; subs[nsub].c1[2]=vals[2]; + // If this is after the first sub-function, c1 of previous was already set. + // The previous sub's c1 is either still default or was set by a /C1 entry. + nsub++; + } + } else { + if (nsub > 0) { + subs[nsub-1].c1[0]=vals[0]; subs[nsub-1].c1[1]=vals[1]; subs[nsub-1].c1[2]=vals[2]; + } + } + } + } + } else if (memcmp(s, "/Bounds", 7) == 0 && (s[7]==' ' || s[7]=='\n' || s[7]=='[')) { + s += 7; + while (s < end && (*s == ' ' || *s == '\n')) s++; + if (s < end && *s == '[') { s++; + while (nbounds < MAX_SUBFUNC - 1 && s < end) { + while (s < end && (*s == ' ' || *s == '\n')) s++; + if (s >= end || *s == ']') break; + const char *ns = s; + while (ns < end && *ns != ' ' && *ns != '\n' && *ns != ']') ns++; + int ti = ns - s; if (ti > 63) ti = 63; + char tmp[64]; memcpy(tmp, s, ti); tmp[ti] = 0; + bounds[nbounds++] = strtof(tmp, NULL); + s = ns; + } + } + } else { + s++; + } + } + } + + // Normalise bounds from absolute to [0,1] range using the shading domain. + { + const char *s = p; + while (s + 8 < end) { + if (memcmp(s, "/Domain", 7) == 0 && (s[7]==' ' || s[7]=='\n' || s[7]=='[')) { + s += 7; + while (s < end && (*s == ' ' || *s == '\n')) s++; + if (s < end && *s == '[') { s++; + float d0=0, d1=100; + char tmp[64]; int ti; + const char *ns = s; + while (ns < end && *ns != ' ' && *ns != '\n' && *ns != ']') ns++; + ti = ns - s; if (ti > 63) ti = 63; + memcpy(tmp, s, ti); tmp[ti]=0; d0 = strtof(tmp, NULL); + s = ns; + while (s < end && (*s == ' ' || *s == '\n')) s++; + ns = s; + while (ns < end && *ns != ' ' && *ns != '\n' && *ns != ']') ns++; + ti = ns - s; if (ti > 63) ti = 63; + memcpy(tmp, s, ti); tmp[ti]=0; d1 = strtof(tmp, NULL); + float dr = (d1 != d0) ? d1 - d0 : 1.0f; + for (int i = 0; i < nbounds; i++) + bounds[i] = (bounds[i] - d0) / dr; + } + break; + } + s++; + } + } + + // Determine shading type: look for /ShadingType + int shade_type = 2; // default axial + { + const char *s = p; + while (s + 13 < end) { + if (memcmp(s, "/ShadingType", 12) == 0) { + s += 12; + while (s < end && (*s == ' ' || *s == '\n')) s++; + if (s < end && *s >= '0' && *s <= '9') { + shade_type = *s - '0'; + } + break; + } + s++; + } + } + + if (shade_type == 2) { + render_axial_shade_full(ctx, dc, st, cx0, cy0, cx1, cy1, + nsub, subs, nbounds > 0 ? bounds : NULL, get_path(ctx, dc)); + } else if (shade_type == 3) { + float cx=cx0, cy=cy0, r0=0, r1; + if (ncoords >= 6) { + r0 = rad_r0; + r1 = rad_r1; + } else { + r1 = (float)sqrt((cx1-cx0)*(cx1-cx0)+(cy1-cy0)*(cy1-cy0)); + } + if (nsub > 0) { + render_radial_shade(ctx, dc, st, cx, cy, r0, r1, + subs, nsub, bounds, nbounds > 0 ? bounds : NULL); + } + } else { + return false; + } + return true; +} + +// Simple wrapper for callers with a single C0/C1 pair. +static void render_axial_shade(fz_context *ctx, dvi_context *dc, dvi_state *st, + float x0, float y0, float x1, float y1, + float c0[3], float c1[3], fz_path *clip_path) +{ + shade_subfunc sf; + sf.c0[0]=c0[0]; sf.c0[1]=c0[1]; sf.c0[2]=c0[2]; + sf.c1[0]=c1[0]; sf.c1[1]=c1[1]; sf.c1[2]=c1[2]; + render_axial_shade_full(ctx, dc, st, x0, y0, x1, y1, 1, &sf, NULL, clip_path); +} + +// Render axial gradient with proper stitching function support. +static void render_axial_shade_full(fz_context *ctx, dvi_context *dc, dvi_state *st, + float x0, float y0, float x1, float y1, + int nsub, const shade_subfunc *sf, const float *bounds, + fz_path *clip_path) +{ + if (!dc->dev) return; + + fz_matrix ctm = dvi_get_ctm(dc, st); + int steps = 500; + + float dx = x1 - x0, dy = y1 - y0; + float len = sqrtf(dx * dx + dy * dy); + if (len < 0.001f) return; + + float px = -dy / len, py = dx / len; + float gx = dx / len, gy = dy / len; + + // Perpendicular half-width from path bounds + float hw = 200.0f; + if (clip_path) { + fz_rect r = fz_bound_path(ctx, clip_path, 0, fz_identity); + if (!fz_is_infinite_rect(r) && !fz_is_empty_rect(r)) { + float max_dist = 0; + float corners[4][2] = {{r.x0, r.y0}, {r.x0, r.y1}, {r.x1, r.y0}, {r.x1, r.y1}}; + for (int c = 0; c < 4; c++) { + float rx = corners[c][0] - x0, ry = corners[c][1] - y0; + float perp = fabsf(ry * dx - rx * dy) / len; + if (perp > max_dist) max_dist = perp; + } + hw = max_dist + 1.0f; + } + } + + float alpha = st->gs.fill_alpha; + bool use_group = (alpha < 0.999f); + if (use_group) { + fz_rect dev_bbox; + if (clip_path) { + fz_rect r = fz_bound_path(ctx, clip_path, 0, fz_identity); + dev_bbox = fz_transform_rect(r, ctm); + } else { + float corners[4][2] = { + {x0 + px * hw, y0 + py * hw}, {x0 - px * hw, y0 - py * hw}, + {x1 + px * hw, y1 + py * hw}, {x1 - px * hw, y1 - py * hw}}; + dev_bbox = fz_empty_rect; + for (int c = 0; c < 4; c++) { + fz_point pt = fz_transform_point_xy(corners[c][0], corners[c][1], ctm); + dev_bbox = fz_include_point_in_rect(dev_bbox, pt); + } + } + fz_begin_group(ctx, dc->dev, dev_bbox, NULL, 1, 0, 0, alpha); + } + + float step_size = len / steps; + float overlap = step_size * 0.5f; + float fill_a = use_group ? 1.0f : alpha; + for (int i = 0; i < steps; i++) + { + float t = (i + 0.5f) / steps; + float color[3]; + shade_eval_color(color, nsub, sf, bounds, t); + + float tv0 = (float)i / steps; + float tv1 = (float)(i + 1) / steps; + float cx = x0 + dx * tv0 - gx * overlap; + float cy = y0 + dy * tv0 - gy * overlap; + float nx = x0 + dx * tv1 + gx * overlap; + float ny = y0 + dy * tv1 + gy * overlap; + + fz_path *path = fz_new_path(ctx); + fz_moveto(ctx, path, cx + px * hw, cy + py * hw); + fz_lineto(ctx, path, nx + px * hw, ny + py * hw); + fz_lineto(ctx, path, nx - px * hw, ny - py * hw); + fz_lineto(ctx, path, cx - px * hw, cy - py * hw); + fz_closepath(ctx, path); + fz_fill_path(ctx, dc->dev, path, 0, ctm, device_cs(ctx), + color, fill_a, color_params); + fz_drop_path(ctx, path); + } + + if (use_group) + fz_end_group(ctx, dc->dev); +} + +// Render a simple radial gradient natively. +static void render_radial_shade(fz_context *ctx, dvi_context *dc, dvi_state *st, + float cx, float cy, float r0, float r1, + const shade_subfunc *sf, int nsub, const float *bounds, int nbounds) +{ + if (!dc->dev) return; + + fz_matrix ctm = dvi_get_ctm(dc, st); + int steps = 400; + float r_max = r1 > r0 ? r1 : r0; + float r_min = r0 < r1 ? r0 : r1; + + float alpha = st->gs.fill_alpha; + bool use_group = (alpha < 0.999f); + if (use_group) { + fz_rect r = {cx - r_max, cy - r_max, cx + r_max, cy + r_max}; + fz_rect dev_bbox = fz_transform_rect(r, ctm); + fz_begin_group(ctx, dc->dev, dev_bbox, NULL, 1, 0, 0, alpha); + } + float fill_a = use_group ? 1.0f : alpha; + + for (int i = 0; i < steps; i++) + { + float t_inner = (float)i / steps; + float t_outer = (float)(i + 1) / steps; + float ri = r_min + (r_max - r_min) * t_inner; + float ro = r_min + (r_max - r_min) * t_outer; + float t = (t_inner + t_outer) * 0.5f; + + float color[3]; + shade_eval_color(color, nsub, sf, bounds, t); + + // Thick ring via even-odd fill + fz_path *path = fz_new_path(ctx); + fz_moveto(ctx, path, cx + ro, cy); + fz_curveto(ctx, path, cx + ro, cy + ro * 0.552f, cx + ro * 0.552f, cy + ro, cx, cy + ro); + fz_curveto(ctx, path, cx - ro * 0.552f, cy + ro, cx - ro, cy + ro * 0.552f, cx - ro, cy); + fz_curveto(ctx, path, cx - ro, cy - ro * 0.552f, cx - ro * 0.552f, cy - ro, cx, cy - ro); + fz_curveto(ctx, path, cx + ro * 0.552f, cy - ro, cx + ro, cy - ro * 0.552f, cx + ro, cy); + fz_closepath(ctx, path); + fz_moveto(ctx, path, cx + ri, cy); + fz_curveto(ctx, path, cx + ri, cy - ri * 0.552f, cx + ri * 0.552f, cy - ri, cx, cy - ri); + fz_curveto(ctx, path, cx - ri * 0.552f, cy - ri, cx - ri, cy - ri * 0.552f, cx - ri, cy); + fz_curveto(ctx, path, cx - ri, cy + ri * 0.552f, cx - ri * 0.552f, cy + ri, cx, cy + ri); + fz_curveto(ctx, path, cx + ri * 0.552f, cy + ri, cx + ri, cy + ri * 0.552f, cx + ri, cy); + fz_closepath(ctx, path); + fz_fill_path(ctx, dc->dev, path, 1, ctm, device_cs(ctx), + color, fill_a, color_params); + fz_drop_path(ctx, path); + } + + if (use_group) + fz_end_group(ctx, dc->dev); +} + +// ---- PS (PostScript) command interpreter for PGF/TikZ ---- +// PGF/TikZ outputs PostScript drawing commands via \special{ps:: ...} +// We maintain a small value stack and a function dictionary. + +#define PS_STACK_MAX 32 +#define PS_FUNC_MAX 16 + +typedef struct { + char name[32]; + char body[256]; + int body_len; +} ps_func_def; + +static float ps_stack[PS_STACK_MAX]; +static int ps_sp = 0; +static ps_func_def ps_funcs[PS_FUNC_MAX]; +static int ps_func_count = 0; + +static void ps_push(float v) { if (ps_sp < PS_STACK_MAX) ps_stack[ps_sp++] = v; } +static float ps_pop() { return ps_sp > 0 ? ps_stack[--ps_sp] : 0.0f; } +static int ps_depth() { return ps_sp; } +static void ps_clear() { ps_sp = 0; } +void ps_state_reset() { + ps_sp = 0; + /* preserve ps_func_count and pdf_pats across pages */ + /* reset runtime pattern state */ + active_pattern.active = 0; + active_pattern.pat_index = -1; + in_pattern_tile = 0; +} + +// ---- PDF Pattern Table ---- +// Stores pattern definitions received via dvipdfmx/xetex specials: +// pdf:stream @pgfpatternobjectN (content) << /Type /Pattern ... >> +// Content is a PDF content stream (using m/l/c/S/f etc.) +#define PDF_PAT_MAX 16 +#define PDF_PAT_BODY 1024 + +typedef struct { + char obj_name[48]; // e.g., "@pgfpatternobject1" + char pat_name[32]; // e.g., "pgfpat1" (mapped via pdf:put) + char content[PDF_PAT_BODY]; // PDF content stream + int content_len; + float bbox[4]; // [llx lly urx ury] + float xstep, ystep; + float matrix[6]; // pattern matrix [a b c d e f] + int paint_type; // 1=colored, 2=uncolored +} pdf_pat_def; + +static pdf_pat_def pdf_pats[PDF_PAT_MAX]; +static int pdf_pat_count = 0; +// in_pattern_tile and active_pattern are defined earlier (before pdf_code) + +// Parse a float value from a string, advancing the pointer +static float parse_pat_float(const char **pp, const char *end) +{ + while (*pp < end && (**pp == ' ' || **pp == '\n' || **pp == '\r')) (*pp)++; + char tmp[64]; int ti = 0; + while (*pp < end && **pp != ' ' && **pp != '\n' && **pp != '\r' && + **pp != '/' && **pp != ']' && **pp != '>' && ti < 63) + tmp[ti++] = *(*pp)++; + tmp[ti] = 0; + return (ti > 0) ? strtof(tmp, NULL) : 0.0f; +} + +// Parse the dictionary part of a pdf:stream special for pattern attributes +static void +parse_pat_dict(pdf_pat_def *pat, const char *dict, int dict_len) +{ + const char *p = dict, *end = dict + dict_len; + pat->bbox[0] = 0; pat->bbox[1] = 0; pat->bbox[2] = 1; pat->bbox[3] = 1; + pat->xstep = 1; pat->ystep = 1; + pat->matrix[0] = 1; pat->matrix[1] = 0; pat->matrix[2] = 0; + pat->matrix[3] = 1; pat->matrix[4] = 0; pat->matrix[5] = 0; + pat->paint_type = 2; + + while (p < end) { + while (p < end && (*p == ' ' || *p == '\n' || *p == '\r')) p++; + if (p >= end) break; + if (p + 10 <= end && memcmp(p, "/PaintType", 10) == 0) { + p += 10; pat->paint_type = (int)parse_pat_float(&p, end); + } else if (p + 5 <= end && memcmp(p, "/BBox", 5) == 0) { + p += 5; while (p < end && *p != '[') p++; + if (p < end) { p++; + for (int i = 0; i < 4; i++) pat->bbox[i] = parse_pat_float(&p, end); + while (p < end && *p != ']') p++; if (p < end) p++; + } + } else if (p + 6 <= end && memcmp(p, "/XStep", 6) == 0) { + p += 6; pat->xstep = parse_pat_float(&p, end); + } else if (p + 6 <= end && memcmp(p, "/YStep", 6) == 0) { + p += 6; pat->ystep = parse_pat_float(&p, end); + } else if (p + 7 <= end && memcmp(p, "/Matrix", 7) == 0) { + p += 7; while (p < end && *p != '[') p++; + if (p < end) { p++; + for (int i = 0; i < 6; i++) pat->matrix[i] = parse_pat_float(&p, end); + while (p < end && *p != ']') p++; if (p < end) p++; + } + } else { p++; } + } +} + +// Store a pattern from a pdf:stream special. +static void +pdf_pat_define(const char *obj_name, int onl, + const char *content, int cl, + const char *dict, int dl) +{ + if (!obj_name || onl < 18) return; + const char *check = obj_name; + if (*check == '@') check++; + if (memcmp(check, "pgfpatternobject", 16) != 0) return; + + pdf_pat_def *pat = NULL; + for (int i = 0; i < pdf_pat_count; i++) { + if ((int)strlen(pdf_pats[i].obj_name) == onl && + memcmp(pdf_pats[i].obj_name, obj_name, onl) == 0) { + pat = &pdf_pats[i]; break; + } + } + if (!pat) { + if (pdf_pat_count >= PDF_PAT_MAX) { + fprintf(stderr, "Warning: PDF pattern table overflow (max %d patterns). Pattern '%.*s' ignored.\n", + PDF_PAT_MAX, onl, obj_name); + return; + } + pat = &pdf_pats[pdf_pat_count++]; + } + int nl = (onl > 47) ? 47 : onl; + memcpy(pat->obj_name, obj_name, nl); pat->obj_name[nl] = 0; + pat->pat_name[0] = 0; + int sl = (cl > PDF_PAT_BODY - 1) ? PDF_PAT_BODY - 1 : cl; + memcpy(pat->content, content, sl); pat->content[sl] = 0; + pat->content_len = sl; + parse_pat_dict(pat, dict, dl); + fprintf(stderr, "[pattern] pdf_pat_define: obj='%s' bbox=[%.1f %.1f %.1f %.1f] " + "step=(%.1f,%.1f) paint_type=%d content_len=%d\n", + pat->obj_name, pat->bbox[0], pat->bbox[1], pat->bbox[2], pat->bbox[3], + pat->xstep, pat->ystep, pat->paint_type, pat->content_len); +} + +// Map /pgfpatN to @pgfpatternobjectN (from pdf:put @pgfpatterns << ... >>) +static void +pdf_pat_map_name(const char *pat_name, int pnl, + const char *obj_name, int onl) +{ + for (int i = 0; i < pdf_pat_count; i++) { + if ((int)strlen(pdf_pats[i].obj_name) == onl && + memcmp(pdf_pats[i].obj_name, obj_name, onl) == 0) { + int ml = (pnl > 31) ? 31 : pnl; + memcpy(pdf_pats[i].pat_name, pat_name, ml); + pdf_pats[i].pat_name[ml] = 0; + fprintf(stderr, "[pattern] pdf_pat_map_name: '%s' -> '%s'\n", + pdf_pats[i].pat_name, pdf_pats[i].obj_name); + return; + } + } +} + +// Look up a pattern by its /pgfpatN name. Returns index or -1. +static int pdf_pat_lookup(const char *pat_name) +{ + for (int i = 0; i < pdf_pat_count; i++) + if (strcmp(pdf_pats[i].pat_name, pat_name) == 0) return i; + return -1; +} + + + +// Forward declaration +static void ps_define_func(const char *name, int nl, const char *body, int bl); +static const char *ps_lookup_func(const char *name); + +// Parse PS function definitions from a string without executing any commands. +// Handles both "def" and "bind def". Used to pre-scan "!" /pgf specials. +static void ps_parse_defs(const char *p, const char *end) +{ + while (p < end) + { + while (p < end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) p++; + if (p >= end) break; + + if (*p != '/') { + // Skip non-definition tokens + while (p < end && *p != ' ' && *p != '\n' && *p != '\r' && *p != '\t' && *p != '/') p++; + continue; + } + p++; // skip '/' + const char *ns = p; + while (p < end && *p != '{' && *p != ' ' && *p != '\n' && *p != '\r' && *p != '\t') p++; + int nl = p - ns; + while (p < end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) p++; + if (p >= end || *p != '{') continue; + p++; // skip '{' + const char *bs = p; + int depth = 1; + while (p < end && depth > 0) { + if (*p == '{') depth++; else if (*p == '}') depth--; + p++; + } + int bl = p - bs - 1; + while (p < end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) p++; + // Handle both "bind def" and "def" + bool is_bind = false; + if (p + 4 <= end && memcmp(p, "bind", 4) == 0) { + is_bind = true; + p += 4; + while (p < end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) p++; + } + if (p + 3 <= end && memcmp(p, "def", 3) == 0) { + p += 3; + if (nl > 0 && bl >= 0) { + // Always overwrite: with function preservation across pages, + // we MUST allow ! specials to clear stale per-element colors + // (pgffc/pgfsc) and redefine library functions after reset. + ps_define_func(ns, nl, bs, bl); + } + } + } +} + +static void ps_define_func(const char *name, int nl, const char *body, int bl) +{ + // Overwrite existing definition with the same name + for (int i = 0; i < ps_func_count; i++) { + if ((int)strlen(ps_funcs[i].name) == nl && memcmp(ps_funcs[i].name, name, nl) == 0) { + if (bl > 255) bl = 255; + memcpy(ps_funcs[i].body, body, bl); + ps_funcs[i].body[bl] = 0; + ps_funcs[i].body_len = bl; + return; + } + } + // New definition + if (ps_func_count >= PS_FUNC_MAX) return; + ps_func_def *f = &ps_funcs[ps_func_count++]; + if (nl > 31) nl = 31; memcpy(f->name, name, nl); f->name[nl] = 0; + if (bl > 255) bl = 255; memcpy(f->body, body, bl); f->body[bl] = 0; + f->body_len = bl; +} + +static const char *ps_lookup_func(const char *name) +{ + for (int i = 0; i < ps_func_count; i++) + if (strcmp(ps_funcs[i].name, name) == 0) return ps_funcs[i].body; + return NULL; +} + +enum { PS_COLOR_FILL=0, PS_COLOR_STROKE=1, PS_COLOR_BOTH=2 }; +static void ps_exec_body(fz_context *ctx, dvi_context *dc, dvi_state *st, + const char *body, int body_len, int color_target); + +// Render a PDF tiling pattern fill using MuPDF's native tiling API. +// Called from pdf_code fill operators (f/F/b/B) when active_pattern is set. +// Clips to the fill path, then uses fz_begin_tile/fz_end_tile to let MuPDF +// handle tile repetition automatically — we only draw tile content once. +static void +render_pdf_pattern_fill(fz_context *ctx, dvi_context *dc, dvi_state *st, int even_odd) +{ + if (!dc->dev || !dc->path || active_pattern.pat_index < 0) return; + + pdf_pat_def *pat = &pdf_pats[active_pattern.pat_index]; + if (pat->content_len <= 0) return; + + // Validate xstep/ystep — use bbox dimensions as fallback + float xstep = pat->xstep > 0 ? pat->xstep : (pat->bbox[2] - pat->bbox[0]); + float ystep = pat->ystep > 0 ? pat->ystep : (pat->bbox[3] - pat->bbox[1]); + if (xstep <= 0 || ystep <= 0) return; + + fprintf(stderr, "[pattern] render_pdf_pattern_fill: pat='%s' bbox=[%.1f %.1f %.1f %.1f] " + "step=(%.1f,%.1f) matrix=[%.2f %.2f %.2f %.2f %.2f %.2f] content_len=%d paint_type=%d\n", + pat->pat_name, pat->bbox[0], pat->bbox[1], pat->bbox[2], pat->bbox[3], + xstep, ystep, + pat->matrix[0], pat->matrix[1], pat->matrix[2], pat->matrix[3], + pat->matrix[4], pat->matrix[5], pat->content_len, pat->paint_type); + + in_pattern_tile = 1; + + // Save graphics state + float saved_fill[3], saved_line[3]; + float saved_fa, saved_sa, saved_lw; + fz_matrix saved_ctm = st->gs.ctm; + int saved_h = st->gs.h, saved_v = st->gs.v; + int saved_clip_depth = st->gs.clip_depth; + memcpy(saved_fill, st->gs.colors.fill, sizeof(saved_fill)); + memcpy(saved_line, st->gs.colors.line, sizeof(saved_line)); + saved_fa = st->gs.fill_alpha; + saved_sa = st->gs.stroke_alpha; + saved_lw = st->gs.line_width; + + // Set pattern color for drawing (uncolored patterns use the color from scn) + if (pat->paint_type == 2) { + color_set_rgb(st->gs.colors.fill, + active_pattern.color[0], active_pattern.color[1], active_pattern.color[2]); + color_set_rgb(st->gs.colors.line, + active_pattern.color[0], active_pattern.color[1], active_pattern.color[2]); + } + + // Detach the fill path — we use it for clipping, not for tile content + fz_path *clip_path = dc->path; + dc->path = NULL; + fz_matrix ctm = dvi_get_ctm(dc, st); + + // Clip to the fill path shape + fz_clip_path(ctx, dc->dev, clip_path, even_odd, ctm, fz_infinite_rect); + + // Build pattern matrix: pattern space → user space → device space + fz_matrix pat_mat; + pat_mat.a = pat->matrix[0]; pat_mat.b = pat->matrix[1]; + pat_mat.c = pat->matrix[2]; pat_mat.d = pat->matrix[3]; + pat_mat.e = pat->matrix[4]; pat_mat.f = pat->matrix[5]; + fz_matrix tile_ctm = fz_concat(pat_mat, ctm); + + // Area to be tiled (fill path bounds in device coordinates) + fz_rect area = fz_bound_path(ctx, clip_path, NULL, ctm); + + // Tile view rectangle (pattern BBox in pattern space) + fz_rect view; + view.x0 = pat->bbox[0]; view.y0 = pat->bbox[1]; + view.x1 = pat->bbox[2]; view.y1 = pat->bbox[3]; + + fz_try(ctx) { + // Use MuPDF's native tiling: draw tile content once, MuPDF repeats it. + // - area: region to cover (device space, after ctm) + // - view: one tile's BBox (pattern space) + // - xstep/ystep: tile spacing (pattern space) + // - tile_ctm: transform from pattern space to device space + fz_begin_tile(ctx, dc->dev, area, view, xstep, ystep, tile_ctm); + + // Set CTM for tile content — pdf_code uses dvi_get_ctm(dc, st) + st->gs.ctm = tile_ctm; + st->gs.h = st->registers.h; + st->gs.v = st->registers.v; + + // Execute tile content stream ONCE — MuPDF tiles it automatically + fz_path *saved_path = dc->path; + dc->path = NULL; + pdf_code(ctx, dc, st, pat->content, pat->content + pat->content_len); + if (dc->path) { drop_path(ctx, dc); } + dc->path = saved_path; + + fz_end_tile(ctx, dc->dev); + } + fz_always(ctx) { + // Pop the clip we pushed above + fz_pop_clip(ctx, dc->dev); + + // Restore the fill path for caller + dc->path = clip_path; + + // Restore graphics state + st->gs.ctm = saved_ctm; + st->gs.h = saved_h; + st->gs.v = saved_v; + st->gs.clip_depth = saved_clip_depth; + memcpy(st->gs.colors.fill, saved_fill, sizeof(saved_fill)); + memcpy(st->gs.colors.line, saved_line, sizeof(saved_line)); + st->gs.fill_alpha = saved_fa; + st->gs.stroke_alpha = saved_sa; + st->gs.line_width = saved_lw; + + in_pattern_tile = 0; + } + fz_catch(ctx) { + // Don't rethrow — pattern rendering failure should not abort the page + fprintf(stderr, "render_pdf_pattern_fill: error rendering pattern tile\n"); + } +} + + +static bool +ps_code(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, cursor_t lim) +{ + const char *p = cur, *end = lim; + + // Track whether a fill/stroke operation was performed that didn't + // drop the path. pgffill leaves the path so that pgfstr can stroke + // it in a \filldraw. But when the special ends and no pgfstr + // followed, we must drop the path ourselves. + bool rendered = false; + + while (p < end) + { + while (p < end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) p++; + if (p >= end) break; + + // Handle function definition: /name{body}def + if (*p == '/') + { + p++; + const char *ns = p; + while (p < end && *p != '{' && *p != ' ') p++; + int nl = p - ns; + while (p < end && *p == ' ') p++; + if (p < end && *p == '{') { + p++; + const char *bs = p; + int depth = 1; + while (p < end && depth > 0) { + if (*p == '{') depth++; else if (*p == '}') depth--; + p++; + } + int bl = p - bs - 1; // without closing } + while (p < end && *p == ' ') p++; + // Support both "def" and "bind def" + bool is_bind = false; + if (p + 4 <= end && memcmp(p, "bind", 4) == 0) { + is_bind = true; + p += 4; + while (p < end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) p++; + } + if (p + 3 <= end && memcmp(p, "def", 3) == 0) { + p += 3; + ps_define_func(ns, nl, bs, bl); + // Immediately execute color function bodies so that colors are + // set even if later ps_lookup_func fails (e.g. due to corruption). + if (nl == 5 && memcmp(ns, "pgffc", 5) == 0) { + // Pattern bodies contain pgfpat whose makepattern/setcolor + // operators are not implemented. Extract color directly. + if (bl >= 6) { + int found = 0; + for (int i = 0; i <= bl - 6; i++) + if (memcmp(bs + i, "pgfpat", 6) == 0) { found = 1; break; } + if (found) { + float pr = 1, pg = 1, pb = 1; + sscanf(bs, "%f %f %f", &pr, &pg, &pb); + color_set_rgb(st->gs.colors.fill, pr, pg, pb); + } else { + ps_exec_body(ctx, dc, st, bs, bl, PS_COLOR_FILL); + } + } else { + ps_exec_body(ctx, dc, st, bs, bl, PS_COLOR_FILL); + } + ps_clear(); + } else if (nl == 5 && memcmp(ns, "pgfsc", 5) == 0) { + ps_exec_body(ctx, dc, st, bs, bl, PS_COLOR_STROKE); + ps_clear(); + } + continue; + } + } + continue; + } + + // Parse token + const char *ts = p; + while (p < end && *p != ' ' && *p != '\n' && *p != '\r' && *p != '\t') p++; + int tl = p - ts; + if (tl == 0) continue; + + char tmp[64]; + if (tl > 63) tl = 63; + memcpy(tmp, ts, tl); tmp[tl] = 0; + + // Try as number (strip [ and ] from PS array syntax like [1.0 or -4.08]) + char *num_start = tmp; + if (*num_start == '[') num_start++; + int num_len = strlen(num_start); + if (num_len > 0 && num_start[num_len-1] == ']') { + num_start[num_len-1] = 0; + num_len--; + } + char *ep; + float fv = strtof(num_start, &ep); + if (ep == num_start + num_len && num_len > 0 && *num_start != '/') + { + ps_push(fv); + continue; + } + + // --- Command dispatch --- + // PS array markers (standalone [ and ]) + if (tmp[0] == '[' && tmp[1] == 0) continue; + if (tmp[0] == ']' && tmp[1] == 0) continue; + // Path building + if (strcmp(tmp, "moveto") == 0) { + if (ps_depth() >= 2) { float y=ps_pop(), x=ps_pop(); fz_moveto(ctx, get_path(ctx,dc), x, y); } + } + else if (strcmp(tmp, "lineto") == 0) { + if (ps_depth() >= 2) { float y=ps_pop(), x=ps_pop(); fz_lineto(ctx, get_path(ctx,dc), x, y); } + } + else if (strcmp(tmp, "curveto") == 0) { + if (ps_depth() >= 6) { + float y3=ps_pop(),x3=ps_pop(), y2=ps_pop(),x2=ps_pop(), y1=ps_pop(),x1=ps_pop(); + fz_curveto(ctx, get_path(ctx,dc), x1,y1, x2,y2, x3,y3); + } + } + else if (strcmp(tmp, "closepath") == 0) { + fz_closepath(ctx, get_path(ctx,dc)); + } + else if (strcmp(tmp, "newpath") == 0) { + drop_path(ctx, dc); + } + // PGF fill / stroke + // IMPORTANT: fill does NOT drop the path — the same path is + // often stroked by a subsequent pgfstr. Path is dropped at + // pgfstr, newpath, or pgfc. + else if (strcmp(tmp, "pgffill") == 0) { + const char *b = ps_lookup_func("pgffc"); + if (b && *b) { + ps_exec_body(ctx, dc, st, b, strlen(b), PS_COLOR_FILL); + } + if (dc->dev) { + float *fc = st->gs.colors.fill; + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_fill_path(ctx, dc->dev, get_path(ctx,dc), 0, ctm, + device_cs(ctx), fc, st->gs.fill_alpha, color_params); + } + rendered = true; + // Do NOT drop path — pgfstr may follow within the same special + } + else if (strcmp(tmp, "pgfstr") == 0) { + const char *b = ps_lookup_func("pgfsc"); + if (b && *b) ps_exec_body(ctx, dc, st, b, strlen(b), PS_COLOR_STROKE); + // When pgfsc is empty, keep current line color (set by inline + // setgray/setrgbcolor/setcmykcolor commands) + float *lc = st->gs.colors.line; + if (dc->dev) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_stroke_state sst; + get_stroke_state(ctx, st, &sst); + fz_stroke_path(ctx, dc->dev, get_path(ctx,dc), &sst, ctm, + device_cs(ctx), lc, st->gs.stroke_alpha, color_params); + } + drop_path(ctx, dc); // stroke is final, drop path + rendered = true; + } + // Graphics state + else if (strcmp(tmp, "gsave") == 0 || strcmp(tmp, "save") == 0) { + if (st->gs_stack.depth < st->gs_stack.limit) { + st->gs_stack.base[st->gs_stack.depth] = st->gs; + st->gs_stack.depth += 1; + } + } + else if (strcmp(tmp, "grestore") == 0 || strcmp(tmp, "restore") == 0) { + if (st->gs_stack.depth > 0) { + int cd0 = st->gs.clip_depth; + st->gs_stack.depth -= 1; + st->gs = st->gs_stack.base[st->gs_stack.depth]; + if (dc->dev) for (int i = st->gs.clip_depth; i < cd0; ++i) fz_pop_clip(ctx, dc->dev); + } + } + // Line width + else if (strcmp(tmp, "pgfw") == 0 || strcmp(tmp, "setlinewidth") == 0) { + if (ps_depth() >= 1) st->gs.line_width = ps_pop(); + } + // Line cap / join + else if (strcmp(tmp, "setlinecap") == 0) { + if (ps_depth() >= 1) st->gs.line_caps = (int)ps_pop(); + } + else if (strcmp(tmp, "setlinejoin") == 0) { + if (ps_depth() >= 1) st->gs.line_join = (int)ps_pop(); + } + // Dash pattern + else if (strcmp(tmp, "setdash") == 0) { + if (ps_depth() >= 1) { + // Pop the dash phase (top of stack) + float ph = ps_pop(); + st->gs.dash_phase = ph; + // Remaining values on the stack are the dash array + // (pushed in order by PS interpreter from [a b c ...]) + int n = ps_depth(); + st->gs.dash_len = n; + if (n > 32) n = 32; + for (int i = 0; i < n; i++) { + st->gs.dash[i] = ps_stack[ps_sp - n + i]; + } + ps_clear(); // consume all array values + } + } + // Inline PS color operators: set the "current color" in PostScript, + // which affects both fill and stroke. We apply them to both fill + // and line colors so that subsequent pgfstr/pgffill use the right + // color even when pgfsc/pgffc are not redefined. + else if (strcmp(tmp, "setgray") == 0) { + if (ps_depth() >= 1) { + float g = ps_pop(); + color_set_gray(st->gs.colors.fill, g); + color_set_gray(st->gs.colors.line, g); + } + } + else if (strcmp(tmp, "setrgbcolor") == 0) { + if (ps_depth() >= 3) { + float b = ps_pop(), g = ps_pop(), r = ps_pop(); + color_set_rgb(st->gs.colors.fill, r, g, b); + color_set_rgb(st->gs.colors.line, r, g, b); + } + } + else if (strcmp(tmp, "setcmykcolor") == 0) { + if (ps_depth() >= 4) { + float k = ps_pop(), y = ps_pop(), m = ps_pop(), c = ps_pop(); + color_set_cmyk(st->gs.colors.fill, c, m, y, k); + color_set_cmyk(st->gs.colors.line, c, m, y, k); + } + } + // PGF pgfe: default rectangle (height, width, x, y) from PS stack + // PGF always redefines pgfe for non-rectangle shapes via other PS commands + else if (strcmp(tmp, "pgfe") == 0) { + if (ps_depth() >= 4) { + float y=ps_pop(), x=ps_pop(), w=ps_pop(), h=ps_pop(); + // Args: bottom→top [height, width, x_start, y_start] + fz_rectto(ctx, get_path(ctx, dc), x, y, x + w, y + h); + } + } + // PS << dictionary start — used by PGF for shading patterns. + // Parse the following dictionary content for shading parameters + // and render natively via try_parse_ps_shading. + else if (strcmp(tmp, "<<") == 0) { + if (try_parse_ps_shading(ctx, dc, st, p, end)) { + drop_path(ctx, dc); + ps_clear(); + rendered = true; + break; + } + } + // PS concat: [a b c d e f] concat — concatenate matrix to CTM + // This is how PGF/dvips driver applies translations and transformations. + // PS coordinates are relative to the page top-left (Y-down), + // device coordinates are relative to page bottom-left (Y-up). + // Conversion: device_y = page_height - 72 - ps_y + else if (strcmp(tmp, "concat") == 0) { + if (ps_depth() >= 6) { + float f=ps_pop(), e=ps_pop(), d=ps_pop(), c=ps_pop(), b=ps_pop(), a=ps_pop(); + fz_matrix mat; + mat.a = a; mat.b = b; mat.c = c; + mat.d = d; mat.e = e; mat.f = f; + // Chain mat to dvi_get_ctm(dc, st) which includes the TeX + // page position (reg.h/reg.v relative to gs.h/gs.v) applied + // to the current CTM. This preserves the TeX positioning + // that would otherwise be lost when gs.h/gs.v are zeroed. + fz_matrix ctm_before = dvi_get_ctm(dc, st); + st->gs.ctm = fz_concat(mat, ctm_before); + st->gs.h = st->registers.h; + st->gs.v = st->registers.v; + ps_clear(); + } + } + // PGF opacity commands + else if (strcmp(tmp, ".pgfsetfillopacityalpha") == 0) { + if (ps_depth() >= 1) st->gs.fill_alpha = ps_pop(); + } + else if (strcmp(tmp, ".pgfsetstrokeopacityalpha") == 0) { + if (ps_depth() >= 1) st->gs.stroke_alpha = ps_pop(); + } + // PS stack operations + else if (strcmp(tmp, "dup") == 0) { + if (ps_depth() >= 1) { float v = ps_pop(); ps_push(v); ps_push(v); } + } + else if (strcmp(tmp, "pop") == 0) { + ps_pop(); // discard top of stack + } + else if (strcmp(tmp, "exch") == 0) { + if (ps_depth() >= 2) { float a=ps_pop(), b=ps_pop(); ps_push(a); ps_push(b); } + } + else if (strcmp(tmp, "neg") == 0) { + if (ps_depth() >= 1) { float v = ps_pop(); ps_push(-v); } + } + // PS currentpoint: push current path point (x,y) onto stack + else if (strcmp(tmp, "currentpoint") == 0) { + fz_point pt = fz_currentpoint(ctx, get_path(ctx, dc)); + ps_push(pt.x); ps_push(pt.y); + } + // PS translate: tx ty translate — apply to full CTM (incl. TeX position) + else if (strcmp(tmp, "translate") == 0) { + if (ps_depth() >= 2) { + float ty=ps_pop(), tx=ps_pop(); + st->gs.ctm = fz_pre_translate(dvi_get_ctm(dc, st), tx, ty); + st->gs.h = st->registers.h; + st->gs.v = st->registers.v; + } + } + // PS scale: sx sy scale — apply to full CTM (incl. TeX position) + else if (strcmp(tmp, "scale") == 0) { + if (ps_depth() >= 2) { + float sy=ps_pop(), sx=ps_pop(); + fz_matrix before = dvi_get_ctm(dc, st); + st->gs.ctm = fz_pre_scale(before, sx, sy); + st->gs.h = st->registers.h; + st->gs.v = st->registers.v; + } + } + // PS rotate: angle rotate — apply to full CTM (incl. TeX position) + else if (strcmp(tmp, "rotate") == 0) { + if (ps_depth() >= 1) { + float angle = ps_pop(); + st->gs.ctm = fz_pre_rotate(dvi_get_ctm(dc, st), angle); + st->gs.h = st->registers.h; + st->gs.v = st->registers.v; + } + } + // PGF /a function: pops x,y, does moveto (initializes path start) + else if (strcmp(tmp, "a") == 0) { + if (ps_depth() >= 2) { + float y=ps_pop(), x=ps_pop(); + fz_moveto(ctx, get_path(ctx, dc), x, y); + } + } + else if (strcmp(tmp, "rlineto") == 0) { + if (ps_depth() >= 2) { + float y=ps_pop(), x=ps_pop(); + fz_path *p = get_path(ctx, dc); + fz_point cp = fz_currentpoint(ctx, p); + fz_lineto(ctx, p, cp.x + x, cp.y + y); + } + } + // PS clip operators: W/W* (PDF names), clip/eoclip (PS names) + // Also handle {clip} {eoclip} — PS code blocks in PGF mask expression + else if (strcmp(tmp, "W") == 0 || strcmp(tmp, "W*") == 0 || + strcmp(tmp, "clip") == 0 || strcmp(tmp, "eoclip") == 0 || + strcmp(tmp, "{clip}") == 0 || strcmp(tmp, "{eoclip}") == 0) { + int eofill = (strcmp(tmp, "W*") == 0 || strcmp(tmp, "eoclip") == 0 || + strcmp(tmp, "{eoclip}") == 0) ? 1 : 0; + if (dc->dev && dc->path) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_clip_path(ctx, dc->dev, dc->path, eofill, ctm, + fz_infinite_rect); + st->gs.clip_depth += 1; + } + drop_path(ctx, dc); + } + else if (strcmp(tmp, "get") == 0 || strcmp(tmp, "ifelse") == 0) { + // PS dict/array get and conditional — not needed, consume 2 values + if (ps_depth() >= 2) { ps_pop(); ps_pop(); } + } + else if (strcmp(tmp, "/pgfsmaskinplace") == 0) { + // PGF soft mask placeholder — no-op for now + } + // PGF dvips stroke/fill aliases + else if (strcmp(tmp, "pgfs") == 0 || strcmp(tmp, "pgfS") == 0) { + // pgfs = PGF stroke (dvips alias for pgfstr) + const char *b = ps_lookup_func("pgfsc"); + if (b && *b) ps_exec_body(ctx, dc, st, b, strlen(b), PS_COLOR_STROKE); + // When pgfsc is empty, keep current line color + if (dc->dev) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_stroke_state sst; + get_stroke_state(ctx, st, &sst); + fz_stroke_path(ctx, dc->dev, get_path(ctx,dc), &sst, ctm, + device_cs(ctx), st->gs.colors.line, st->gs.stroke_alpha, color_params); + } + drop_path(ctx, dc); + rendered = true; + } + else if (strcmp(tmp, "pgfr") == 0 || strcmp(tmp, "pgfR") == 0) { + // pgfr = PGF fill (dvips alias for pgffill) + const char *b = ps_lookup_func("pgffc"); + if (b && *b) { + ps_exec_body(ctx, dc, st, b, strlen(b), PS_COLOR_FILL); + } + if (dc->dev) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_fill_path(ctx, dc->dev, get_path(ctx,dc), 0, ctm, + device_cs(ctx), st->gs.colors.fill, st->gs.fill_alpha, color_params); + } + rendered = true; + // Do NOT drop path — pgfstr may follow within the same special + } + // PGF cleanup / end markers + else if (strcmp(tmp, "pgfc") == 0 || strcmp(tmp, "pgfo") == 0) { /* no-op */ } + // PGF shading function invocations — intercept for native rendering. + // These are called via ps:: specials with params on the PS stack. + // We handle them before the general ps_lookup_func fallback because + // ps_exec_body cannot execute the shading PS operators natively, + // which would leave the path filled with the default (black) color. + else if (strcmp(tmp, "pgfHrgb") == 0 || strcmp(tmp, "pgfVrgb") == 0 || + strcmp(tmp, "pgfArgb") == 0) { + // Axial RGB: sx sy ex ey R1 G1 B1 R2 G2 B2 depth (11 params) + if (ps_depth() >= 11) { + float depth = ps_pop(); + float b2=ps_pop(), g2=ps_pop(), r2=ps_pop(); + float b1=ps_pop(), g1=ps_pop(), r1=ps_pop(); + float ey=ps_pop(), ex=ps_pop(), sy=ps_pop(), sx=ps_pop(); + float c0[3] = {r1, g1, b1}; + float c1[3] = {r2, g2, b2}; + (void)depth; + render_axial_shade(ctx, dc, st, sx, sy, ex, ey, c0, c1, NULL); + ps_clear(); + rendered = true; + } + } + else if (strcmp(tmp, "pgfHcmyk") == 0 || strcmp(tmp, "pgfVcmyk") == 0 || + strcmp(tmp, "pgfAcmyk") == 0) { + // Axial CMYK: sx sy ex ey C1 M1 Y1 K1 C2 M2 Y2 K2 depth (13 params) + if (ps_depth() >= 13) { + float depth = ps_pop(); + float k2=ps_pop(), y2=ps_pop(), m2=ps_pop(), c2_c=ps_pop(); + float k1=ps_pop(), y1=ps_pop(), m1=ps_pop(), c1_c=ps_pop(); + float ey=ps_pop(), ex=ps_pop(), sy=ps_pop(), sx=ps_pop(); + (void)depth; + float c0[3], c1[3]; + color_set_cmyk(c0, c1_c, m1, y1, k1); + color_set_cmyk(c1, c2_c, m2, y2, k2); + render_axial_shade(ctx, dc, st, sx, sy, ex, ey, c0, c1, NULL); + ps_clear(); + rendered = true; + } + } + else if (strcmp(tmp, "pgfRrgb") == 0 || strcmp(tmp, "pgfR1rgb") == 0) { + // Radial RGB: sx sy sr ex ey er R1 G1 B1 R2 G2 B2 (12 params) + if (ps_depth() >= 12) { + float b2=ps_pop(), g2=ps_pop(), r2=ps_pop(); + float b1=ps_pop(), g1=ps_pop(), r1=ps_pop(); + float er=ps_pop(), ey=ps_pop(), ex=ps_pop(); + float sr=ps_pop(), sy=ps_pop(), sx=ps_pop(); + float c0[3] = {r1, g1, b1}; + float c1[3] = {r2, g2, b2}; + { + shade_subfunc sf = {{c0[0],c0[1],c0[2]},{c1[0],c1[1],c1[2]}}; + render_radial_shade(ctx, dc, st, sx, sy, sr, er, &sf, 1, NULL, 0); + } + ps_clear(); + rendered = true; + } + } + // Try as a user-defined function call + else { + const char *b = ps_lookup_func(tmp); + if (b) { + ps_exec_body(ctx, dc, st, b, strlen(b), PS_COLOR_BOTH); + ps_clear(); // user functions must not leak stack values + rendered = true; // user-defined functions typically render + } + } + } + + // Drop path if a fill/stroke operation left it behind + // (pgffill keeps the path for a possible pgfstr within the same + // special; pgfstr already drops it). Without this, the next + // drawing operation inherits stale path segments. + if (rendered && dc->path) + drop_path(ctx, dc); + + ps_clear(); + return 1; +} + +// Execute a stored PS function body (simple token interpreter) +// The stack should already have the function arguments; body consumes them. +// Do NOT save/restore ps_sp — let the body naturally consume and push values. +static void +ps_exec_body(fz_context *ctx, dvi_context *dc, dvi_state *st, + const char *body, int body_len, int color_target) +{ + const char *p = body, *end = body + body_len; + bool rendered = false; + while (p < end) + { + while (p < end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) p++; + if (p >= end) break; + + const char *ts = p; + while (p < end && *p != ' ' && *p != '\n' && *p != '\r' && *p != '\t') p++; + int tl = p - ts; + if (tl == 0) continue; + + char tmp[64]; + if (tl > 63) tl = 63; + memcpy(tmp, ts, tl); tmp[tl] = 0; + + // Strip [ and ] from PS array syntax + char *num_start = tmp; + if (*num_start == '[') num_start++; + int num_len = strlen(num_start); + if (num_len > 0 && num_start[num_len-1] == ']') { + num_start[num_len-1] = 0; + num_len--; + } + char *ep; + float fv = strtof(num_start, &ep); + if (ep == num_start + num_len && num_len > 0 && *num_start != '/') { + ps_push(fv); + continue; + } + + int ct = color_target; + // PS array markers + if (tmp[0] == '[' && tmp[1] == 0) continue; + if (tmp[0] == ']' && tmp[1] == 0) continue; + // Same commands as ps_code but without function defs + if (strcmp(tmp, "setgray") == 0) { + if (ps_depth() >= 1) { float g=ps_pop(); + if (ct != PS_COLOR_STROKE) color_set_gray(st->gs.colors.fill,g); + if (ct != PS_COLOR_FILL) color_set_gray(st->gs.colors.line,g); + } + } + else if (strcmp(tmp, "setrgbcolor") == 0) { + if (ps_depth() >= 3) { float b=ps_pop(),g=ps_pop(),r=ps_pop(); + if (ct != PS_COLOR_STROKE) color_set_rgb(st->gs.colors.fill,r,g,b); + if (ct != PS_COLOR_FILL) color_set_rgb(st->gs.colors.line,r,g,b); + } + } + else if (strcmp(tmp, "setcmykcolor") == 0) { + if (ps_depth() >= 4) { float k=ps_pop(),y=ps_pop(),m=ps_pop(),c=ps_pop(); + if (ct != PS_COLOR_STROKE) color_set_cmyk(st->gs.colors.fill,c,m,y,k); + if (ct != PS_COLOR_FILL) color_set_cmyk(st->gs.colors.line,c,m,y,k); + } + } + else if (strcmp(tmp, "fillopacity") == 0) { + if (ps_depth() >= 1) st->gs.fill_alpha = ps_pop(); + } + else if (strcmp(tmp, "strokeopacity") == 0) { + if (ps_depth() >= 1) st->gs.stroke_alpha = ps_pop(); + } + else if (strcmp(tmp, "pgfw") == 0 || strcmp(tmp, "setlinewidth") == 0) { + if (ps_depth() >= 1) st->gs.line_width = ps_pop(); + } + else if (strcmp(tmp, "setlinecap") == 0) { + if (ps_depth() >= 1) st->gs.line_caps = (int)ps_pop(); + } + else if (strcmp(tmp, "setlinejoin") == 0) { + if (ps_depth() >= 1) st->gs.line_join = (int)ps_pop(); + } + else if (strcmp(tmp, "setdash") == 0) { + if (ps_depth() >= 1) { + float ph = ps_pop(); + st->gs.dash_phase = ph; + int n = ps_depth(); + st->gs.dash_len = n; + if (n > 32) n = 32; + for (int i = 0; i < n; i++) + st->gs.dash[i] = ps_stack[ps_sp - n + i]; + ps_sp -= n; // only consume the dash array values + } + } + // Path building commands (used in pgf function bodies like pgf1-pgf8) + else if (strcmp(tmp, "moveto") == 0) { + if (ps_depth() >= 2) { float y=ps_pop(), x=ps_pop(); fz_moveto(ctx, get_path(ctx,dc), x, y); } + } + else if (strcmp(tmp, "lineto") == 0) { + if (ps_depth() >= 2) { float y=ps_pop(), x=ps_pop(); fz_lineto(ctx, get_path(ctx,dc), x, y); } + } + else if (strcmp(tmp, "curveto") == 0) { + if (ps_depth() >= 6) { + float y3=ps_pop(),x3=ps_pop(), y2=ps_pop(),x2=ps_pop(), y1=ps_pop(),x1=ps_pop(); + fz_curveto(ctx, get_path(ctx,dc), x1,y1, x2,y2, x3,y3); + } + } + else if (strcmp(tmp, "closepath") == 0) { + fz_closepath(ctx, get_path(ctx,dc)); + } + else if (strcmp(tmp, "newpath") == 0) { + drop_path(ctx, dc); + } + // Graphics state (save/restore within function bodies) + else if (strcmp(tmp, "gsave") == 0 || strcmp(tmp, "save") == 0) { + if (st->gs_stack.depth < st->gs_stack.limit) { + st->gs_stack.base[st->gs_stack.depth] = st->gs; + st->gs_stack.depth += 1; + } + } + else if (strcmp(tmp, "grestore") == 0 || strcmp(tmp, "restore") == 0) { + if (st->gs_stack.depth > 0) { + int cd0 = st->gs.clip_depth; + st->gs_stack.depth -= 1; + st->gs = st->gs_stack.base[st->gs_stack.depth]; + if (dc->dev) for (int i = st->gs.clip_depth; i < cd0; ++i) fz_pop_clip(ctx, dc->dev); + } + } + // PS clip within function bodies + else if (strcmp(tmp, "clip") == 0 || strcmp(tmp, "eoclip") == 0 || + strcmp(tmp, "W") == 0 || strcmp(tmp, "W*") == 0 || + strcmp(tmp, "{clip}") == 0 || strcmp(tmp, "{eoclip}") == 0) { + int eofill = (strcmp(tmp, "W*") == 0 || strcmp(tmp, "eoclip") == 0 || + strcmp(tmp, "{eoclip}") == 0) ? 1 : 0; + if (dc->dev && dc->path) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_clip_path(ctx, dc->dev, dc->path, eofill, ctm, fz_infinite_rect); + st->gs.clip_depth += 1; + } + drop_path(ctx, dc); + } + // Stroke/fill within function bodies + else if (strcmp(tmp, "pgfstr") == 0) { + if (dc->dev) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_stroke_state sst; + get_stroke_state(ctx, st, &sst); + fz_stroke_path(ctx, dc->dev, get_path(ctx,dc), &sst, ctm, + device_cs(ctx), st->gs.colors.line, st->gs.stroke_alpha, color_params); + } + drop_path(ctx, dc); // stroke is final + rendered = true; + } + else if (strcmp(tmp, "pgffill") == 0) { + if (dc->dev) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_fill_path(ctx, dc->dev, get_path(ctx,dc), 0, ctm, + device_cs(ctx), st->gs.colors.fill, st->gs.fill_alpha, color_params); + } + rendered = true; + // Do NOT drop path — pgfstr may follow + } + // concat for CTM transforms within function bodies. + // Chain to dvi_get_ctm so the TeX page position is included, + // same rationale as the top-level concat handler in ps_code. + else if (strcmp(tmp, "concat") == 0) { + if (ps_depth() >= 6) { + float f=ps_pop(), e=ps_pop(), d=ps_pop(), c=ps_pop(), b=ps_pop(), a=ps_pop(); + fz_matrix mat; + mat.a = a; mat.b = b; mat.c = c; + mat.d = d; mat.e = e; mat.f = f; + st->gs.ctm = fz_concat(mat, dvi_get_ctm(dc, st)); + st->gs.h = st->registers.h; + st->gs.v = st->registers.v; + // 6 values already popped, stack is correct + } + } + // PGF opacity within function bodies + else if (strcmp(tmp, ".pgfsetfillopacityalpha") == 0) { + if (ps_depth() >= 1) st->gs.fill_alpha = ps_pop(); + } + else if (strcmp(tmp, ".pgfsetstrokeopacityalpha") == 0) { + if (ps_depth() >= 1) st->gs.stroke_alpha = ps_pop(); + } + // PS stack ops within function bodies + else if (strcmp(tmp, "dup") == 0) { + if (ps_depth() >= 1) { float v = ps_pop(); ps_push(v); ps_push(v); } + } + else if (strcmp(tmp, "pop") == 0) { + ps_pop(); + } + else if (strcmp(tmp, "exch") == 0) { + if (ps_depth() >= 2) { float a=ps_pop(), b=ps_pop(); ps_push(a); ps_push(b); } + } + else if (strcmp(tmp, "neg") == 0) { + if (ps_depth() >= 1) { float v = ps_pop(); ps_push(-v); } + } + // PS currentpoint / translate / scale / rotate (used by graphicx) + else if (strcmp(tmp, "currentpoint") == 0) { + fz_point pt = fz_currentpoint(ctx, get_path(ctx, dc)); + ps_push(pt.x); ps_push(pt.y); + } + else if (strcmp(tmp, "translate") == 0) { + if (ps_depth() >= 2) { + float ty=ps_pop(), tx=ps_pop(); + st->gs.ctm = fz_pre_translate(dvi_get_ctm(dc, st), tx, ty); + st->gs.h = st->registers.h; + st->gs.v = st->registers.v; + } + } + else if (strcmp(tmp, "scale") == 0) { + if (ps_depth() >= 2) { + float sy=ps_pop(), sx=ps_pop(); + st->gs.ctm = fz_pre_scale(dvi_get_ctm(dc, st), sx, sy); + st->gs.h = st->registers.h; + st->gs.v = st->registers.v; + } + } + else if (strcmp(tmp, "rotate") == 0) { + if (ps_depth() >= 1) { + float angle = ps_pop(); + st->gs.ctm = fz_pre_rotate(dvi_get_ctm(dc, st), angle); + st->gs.h = st->registers.h; + st->gs.v = st->registers.v; + } + } + else if (strcmp(tmp, "a") == 0) { + // PGF /a function: pops x,y, does moveto + if (ps_depth() >= 2) { + float y=ps_pop(), x=ps_pop(); + fz_moveto(ctx, get_path(ctx, dc), x, y); + } + } + else if (strcmp(tmp, "rlineto") == 0) { + if (ps_depth() >= 2) { + float y=ps_pop(), x=ps_pop(); + fz_path *p = get_path(ctx, dc); + fz_point cp = fz_currentpoint(ctx, p); + fz_lineto(ctx, p, cp.x + x, cp.y + y); + } + } + // PS clip operators in function bodies + else if (strcmp(tmp, "W") == 0 || strcmp(tmp, "W*") == 0) { + int eofill = (tmp[0] == 'W' && tmp[1] == '*') ? 1 : 0; + if (dc->dev) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_clip_path(ctx, dc->dev, get_path(ctx, dc), eofill, ctm, + fz_infinite_rect); + st->gs.clip_depth += 1; + } + drop_path(ctx, dc); + } + else if (strcmp(tmp, "get") == 0 || strcmp(tmp, "ifelse") == 0) { + if (ps_depth() >= 2) { ps_pop(); ps_pop(); } + } + // PGF dvips stroke/fill aliases in function bodies + else if (strcmp(tmp, "pgfs") == 0 || strcmp(tmp, "pgfS") == 0) { + if (dc->dev) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_stroke_state sst; + get_stroke_state(ctx, st, &sst); + fz_stroke_path(ctx, dc->dev, get_path(ctx,dc), &sst, ctm, + device_cs(ctx), st->gs.colors.line, st->gs.stroke_alpha, color_params); + } + drop_path(ctx, dc); + rendered = true; + } + else if (strcmp(tmp, "pgfr") == 0 || strcmp(tmp, "pgfR") == 0) { + if (dc->dev) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_fill_path(ctx, dc->dev, get_path(ctx,dc), 0, ctm, + device_cs(ctx), st->gs.colors.fill, st->gs.fill_alpha, color_params); + } + rendered = true; + // Do NOT drop path — pgfstr may follow within the same body + } + // Raw PS stroke/fill (used in PaintProc bodies) + else if (strcmp(tmp, "stroke") == 0) { + if (dc->dev && dc->path) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_stroke_state sst; + get_stroke_state(ctx, st, &sst); + fz_stroke_path(ctx, dc->dev, dc->path, &sst, ctm, + device_cs(ctx), st->gs.colors.line, st->gs.stroke_alpha, color_params); + } + drop_path(ctx, dc); + rendered = true; + } + else if (strcmp(tmp, "fill") == 0 || strcmp(tmp, "eofill") == 0) { + if (dc->dev && dc->path) { + int eofill = (tmp[0] == 'e') ? 1 : 0; + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_fill_path(ctx, dc->dev, dc->path, eofill, ctm, + device_cs(ctx), st->gs.colors.fill, st->gs.fill_alpha, color_params); + } + drop_path(ctx, dc); + rendered = true; + } + // Pattern/dict-related no-ops (appear in pattern body wrappers) + else if (strcmp(tmp, "begin") == 0 || strcmp(tmp, "end") == 0 || + strcmp(tmp, "dict") == 0 || strcmp(tmp, "bind") == 0 || + strcmp(tmp, "def") == 0 || strcmp(tmp, "initgraphics") == 0 || + strcmp(tmp, "setcolorspace") == 0 || strcmp(tmp, "setcolor") == 0 || + strcmp(tmp, "makepattern") == 0 || strcmp(tmp, "setpattern") == 0 || + strcmp(tmp, "matrix") == 0) { + // no-op: these are pattern machinery commands we don't need + } + // ignore other commands in body + } + + // Drop path if a fill left it behind (same rationale as ps_code) + if (rendered && dc->path) + drop_path(ctx, dc); +} + +// Parse a pgf PostScript shading special and render natively. +// Handles common shading types: axial (pgfHrgb/Vrgb/Argb) and radial (pgfRrgb). +// Returns 1 if handled, 0 if not. +static bool +dvi_exec_pgf_shading(fz_context *ctx, dvi_context *dc, dvi_state *st, + cursor_t cur, cursor_t lim) +{ + // Scan for known shading function invocations. + // Skip past function definitions (between { and } bind def). + // Look for: param1 param2 ... paramN funcName + + // Known shading functions and their parameter counts + // Axial: 11 params (startx starty endx endy R1 G1 B1 R2 G2 B2 depth) + funcName + // Radial: more complex + + // Simple approach: scan for function names and parse backward for params + static const char *func_names[] = { + "pgfHrgb", "pgfVrgb", "pgfArgb", // axial RGB + "pgfHcmyk", "pgfVcmyk", "pgfAcmyk", // axial CMYK + "pgfRrgb", "pgfR1rgb", // radial RGB + NULL + }; + + for (int fi = 0; func_names[fi]; fi++) + { + const char *fname = func_names[fi]; + int flen = strlen(fname); + + // Search for function name in the special content + for (cursor_t p = cur; p + flen <= lim; p++) + { + if (memcmp(p, fname, flen) == 0) + { + // Check that it's a standalone token (preceded by whitespace/newline) + if (p > cur && !(*(p-1) == ' ' || *(p-1) == '\n' || *(p-1) == '\r')) + continue; + // Check end of token + if (p + flen < lim && !(*(p+flen) == ' ' || *(p+flen) == '\n' || *(p+flen) == '\r')) + continue; + + // Parse backwards to get parameters + // Skip the function definition if this is a 'def' not an invocation + cursor_t after = p + flen; + // Skip trailing whitespace + while (after < lim && (*after == ' ' || *after == '\n' || *after == '\r')) + after++; + + // This is an invocation if not followed by '{' or 'bind def' + // For now, try to parse parameters before the function name + float params[20] = {0}; + int nparams = 0; + cursor_t q = p; + + // Scan backwards to find start of numeric parameters + while (q > cur && nparams < 15) + { + // Skip whitespace backwards + while (q > cur && (*(q-1) == ' ' || *(q-1) == '\n' || *(q-1) == '\r')) + q--; + + // Find start of this token + cursor_t tok_start = q; + while (tok_start > cur && *(tok_start-1) != ' ' && *(tok_start-1) != '\n' + && *(tok_start-1) != '\r' && *(tok_start-1) != '{' && *(tok_start-1) != '}') + tok_start--; + + // Try to parse as float + char tmp[64]; + int tlen = q - tok_start; + if (tlen > 63) break; + memcpy(tmp, tok_start, tlen); + tmp[tlen] = 0; + + // Check if this is a number + bool is_num = true; + int dots = 0; + for (int k = 0; k < tlen; k++) + { + if (tmp[k] == '-') { if (k > 0) { is_num = false; break; } } + else if (tmp[k] == '.') { dots++; if (dots > 1) { is_num = false; break; } } + else if (tmp[k] < '0' || tmp[k] > '9') { is_num = false; break; } + } + if (!is_num || tlen == 0) break; + + params[nparams++] = pfloat(tok_start, q); + q = tok_start; + } + + if (nparams < 2) continue; + + // Reverse params (we parsed them backwards) + for (int i = 0; i < nparams / 2; i++) + { + float tmp2 = params[i]; + params[i] = params[nparams - 1 - i]; + params[nparams - 1 - i] = tmp2; + } + + // Check if this is an axial shading + bool is_axial = (fi <= 2); // pgfHrgb, pgfVrgb, pgfArgb + bool is_cmyk = (fi >= 3 && fi <= 5); + bool is_radial = (fi >= 6); + + if (is_axial && nparams >= 11) + { + float sx = params[0], sy = params[1]; + float ex = params[2], ey = params[3]; + float c0[3], c1[3]; + + if (is_cmyk) + { + color_set_cmyk(c0, params[4], params[5], params[6], params[7]); + color_set_cmyk(c1, params[8], params[9], params[10], params[11]); + } + else + { + color_set_rgb(c0, params[4], params[5], params[6]); + color_set_rgb(c1, params[8], params[9], params[10]); + } + + render_axial_shade(ctx, dc, st, sx, sy, ex, ey, c0, c1, NULL); + return 1; + } + + if (is_radial && nparams >= 10) + { + // Radial: startx starty startr endx endy endr R1 G1 B1 R2 G2 B2 + float sx = params[0], sy = params[1]; + float ex = params[2], ey = params[3]; + float c0[3] = {params[4], params[5], params[6]}; + float c1[3] = {params[7], params[8], params[9]}; + float r0 = 0, r1 = sqrtf((ex - sx) * (ex - sx) + (ey - sy) * (ey - sy)); + + { + shade_subfunc sf = {{c0[0],c0[1],c0[2]},{c1[0],c1[1],c1[2]}}; + render_radial_shade(ctx, dc, st, sx, sy, r0, r1, &sf, 1, NULL, 0); + } + return 1; + } + + // If we found a function name but couldn't parse params, still count as handled + return 1; + } + } + } + + // Not a recognized shading pattern + return 1; // Still return 1 to avoid aborting the page +} + static bool dvi_exec_pdf(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, cursor_t lim) { cursor_t mar, i, f0, f1, f2, f3, f4, f5, pxform = NULL, pstart, pend; - -#line 3270 "dvi_special.c" + +#line 5458 "dvi_special.c" { int yych; unsigned int yyaccept = 0; static const unsigned char yybm[] = { - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 160, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, + 0, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 208, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 96, 96, 96, 96, 96, 96, 96, 96, + 96, 96, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, }; mar = cur; yych = cur < lim ? *cur : -1; switch (yych) { - case 'b': goto yy271; - case 'c': goto yy273; - case 'e': goto yy274; + case 'Q': goto yy271; + case 'b': goto yy272; + case 'c': goto yy274; + case 'e': goto yy275; case 'i': yyt1 = cur; - goto yy275; - case 'p': goto yy276; + goto yy276; + case 'l': goto yy277; + case 'o': goto yy278; + case 'p': goto yy279; + case 'q': goto yy280; + case 's': goto yy281; + case 'u': goto yy282; default: goto yy270; } yy270: -#line 1207 "dvi_special.re2c.c" +#line 3541 "dvi_special.re2c.c" { return unhandled("pdf special", cur, lim, 0); } -#line 3323 "dvi_special.c" +#line 5517 "dvi_special.c" yy271: ++cur; +#line 3413 "dvi_special.re2c.c" + { + if (st->gs_stack.depth == 0) return 0; + int clip_depth0 = st->gs.clip_depth; + st->gs_stack.depth -= 1; + st->gs = st->gs_stack.base[st->gs_stack.depth]; + if (dc->dev) + for (int i = st->gs.clip_depth; i < clip_depth0; ++i) + fz_pop_clip(ctx, dc->dev); + return 1; + } +#line 5531 "dvi_special.c" +yy272: + ++cur; yych = cur < lim ? *cur : -1; - if (yych <= 'd') { - if (yych == 'c') goto yy277; + if (yych <= 'e') { + if (yych == 'c') goto yy283; + if (yych >= 'e') goto yy284; } else { - if (yych <= 'e') goto yy278; - if (yych == 't') goto yy279; + if (yych <= 't') { + if (yych >= 't') goto yy285; + } else { + if (yych == 'x') goto yy287; + } } -yy272: +yy273: cur = mar; - if (yyaccept <= 1) { - if (yyaccept == 0) { - goto yy270; + if (yyaccept <= 2) { + if (yyaccept <= 1) { + if (yyaccept == 0) { + goto yy270; + } else { + goto yy286; + } } else { - goto yy280; + goto yy291; } } else { - if (yyaccept == 2) { - goto yy283; + if (yyaccept == 3) { + goto yy294; } else { - goto yy286; + goto yy409; } } -yy273: +yy274: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'o') goto yy281; - goto yy272; -yy274: + if (yych == 'm') goto yy288; + if (yych == 'o') goto yy289; + goto yy273; +yy275: ++cur; yych = cur < lim ? *cur : -1; - if (yych <= 'm') { - if (yych == 'c') goto yy282; - goto yy272; + if (yych <= 'n') { + if (yych == 'c') goto yy290; + if (yych <= 'm') goto yy273; + goto yy292; } else { - if (yych <= 'n') goto yy284; - if (yych == 't') goto yy285; - goto yy272; + if (yych <= 't') { + if (yych <= 's') goto yy273; + goto yy293; + } else { + if (yych == 'x') goto yy295; + goto yy273; + } } -yy275: +yy276: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'm') goto yy287; - goto yy272; -yy276: + if (yych == 'm') goto yy296; + goto yy273; +yy277: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'i') goto yy297; + goto yy273; +yy278: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'b') goto yy298; + goto yy273; +yy279: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'a') goto yy299; + if (yych == 'u') goto yy300; + goto yy273; +yy280: + ++cur; +#line 3405 "dvi_special.re2c.c" + { + if (st->gs_stack.depth >= st->gs_stack.limit) return 0; + st->gs_stack.base[st->gs_stack.depth] = st->gs; + st->gs_stack.depth += 1; + return 1; + } +#line 5616 "dvi_special.c" +yy281: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 't') goto yy301; + goto yy273; +yy282: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'a') goto yy288; - goto yy272; -yy277: + if (yych == 'x') goto yy302; + goto yy273; +yy283: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'o') goto yy294; - goto yy290; -yy278: + if (yych == 'o') goto yy308; + goto yy304; +yy284: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'g') goto yy295; - goto yy272; -yy279: + if (yych == 'g') goto yy309; + goto yy273; +yy285: yyaccept = 1; ++cur; mar = cur; yych = cur < lim ? *cur : -1; - if (yych == 'r') goto yy298; - goto yy297; -yy280: -#line 1171 "dvi_special.re2c.c" + if (yych == 'r') goto yy312; + goto yy311; +yy286: +#line 3359 "dvi_special.re2c.c" { return pdf_btrans(dc, st, cur, lim); } -#line 3394 "dvi_special.c" -yy281: +#line 5647 "dvi_special.c" +yy287: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'd') goto yy299; - goto yy272; -yy282: + if (yych == 'o') goto yy313; + goto yy273; +yy288: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == ' ') goto yy314; + goto yy273; +yy289: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'd') goto yy315; + goto yy273; +yy290: yyaccept = 2; ++cur; mar = cur; yych = cur < lim ? *cur : -1; - if (yych == 'o') goto yy300; -yy283: -#line 1199 "dvi_special.re2c.c" + if (yych == 'o') goto yy316; +yy291: +#line 3400 "dvi_special.re2c.c" { return colorstack_pop(ctx, dc, st, -1); } -#line 3411 "dvi_special.c" -yy284: +#line 5674 "dvi_special.c" +yy292: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'd') goto yy301; - goto yy272; -yy285: + if (yych == 'd') goto yy317; + goto yy273; +yy293: yyaccept = 3; ++cur; mar = cur; yych = cur < lim ? *cur : -1; - if (yych == 'r') goto yy302; -yy286: -#line 1174 "dvi_special.re2c.c" + if (yych == 'r') goto yy318; +yy294: +#line 3362 "dvi_special.re2c.c" { return pdf_etrans(dc, st); } -#line 3426 "dvi_special.c" -yy287: +#line 5689 "dvi_special.c" +yy295: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'a') goto yy303; - goto yy272; -yy288: + if (yych == 'o') goto yy319; + goto yy273; +yy296: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'g') goto yy304; - goto yy272; -yy289: + if (yych == 'a') goto yy320; + goto yy273; +yy297: ++cur; yych = cur < lim ? *cur : -1; -yy290: - if (yybm[0+yych] & 32) { - goto yy289; + if (yych == 't') goto yy321; + goto yy273; +yy298: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'j') goto yy322; + goto yy273; +yy299: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'g') goto yy323; + goto yy273; +yy300: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 't') goto yy324; + goto yy273; +yy301: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'r') goto yy325; + goto yy273; +yy302: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'o') goto yy326; + goto yy273; +yy303: + ++cur; + yych = cur < lim ? *cur : -1; +yy304: + if (yybm[0+yych] & 16) { + goto yy303; } if (yych <= '/') { - if (yych != '-') goto yy272; + if (yych != '-') goto yy273; yyt1 = cur; } else { if (yych <= '9') { yyt1 = cur; - goto yy291; + goto yy305; } - if (yych == '[') goto yy293; - goto yy272; + if (yych == '[') goto yy307; + goto yy273; } ++cur; yych = cur < lim ? *cur : -1; - if (yybm[0+yych] & 64) { - goto yy291; + if (yybm[0+yych] & 32) { + goto yy305; } - goto yy272; -yy291: + goto yy273; +yy305: ++cur; yych = cur < lim ? *cur : -1; - if (yybm[0+yych] & 64) { - goto yy291; + if (yybm[0+yych] & 32) { + goto yy305; } - if (yych == '.') goto yy305; + if (yych == '.') goto yy327; yyt2 = NULL; yyt3 = NULL; yyt4 = NULL; yyt5 = NULL; -yy292: +yy306: f4 = yyt1; f0 = yyt2; f1 = yyt3; f2 = yyt4; f3 = yyt5; -#line 1186 "dvi_special.re2c.c" +#line 3378 "dvi_special.re2c.c" { if (!colorstack_push(ctx, dc, st, -1)) return 0; if (f3) + { color_set_cmyk(st->gs.colors.fill, pfloat(f0, lim), pfloat(f1, lim), pfloat(f2, lim), pfloat(f3, lim)); + color_set_cmyk(st->gs.colors.line, pfloat(f0, lim), pfloat(f1, lim), pfloat(f2, lim), pfloat(f3, lim)); + } else if (f1) + { color_set_rgb(st->gs.colors.fill, pfloat(f0, lim), pfloat(f1, lim), pfloat(f2, lim)); + color_set_rgb(st->gs.colors.line, pfloat(f0, lim), pfloat(f1, lim), pfloat(f2, lim)); + } else + { color_set_gray(st->gs.colors.fill, pfloat(f4 ? f4 : f0, lim)); + color_set_gray(st->gs.colors.line, pfloat(f4 ? f4 : f0, lim)); + } return 1; } -#line 3490 "dvi_special.c" -yy293: +#line 5792 "dvi_special.c" +yy307: ++cur; yych = cur < lim ? *cur : -1; if (yych <= ',') { - if (yych == ' ') goto yy293; - goto yy272; + if (yych == ' ') goto yy307; + goto yy273; } else { if (yych <= '-') { yyt2 = cur; - goto yy306; + goto yy328; } - if (yych <= '/') goto yy272; + if (yych <= '/') goto yy273; if (yych <= '9') { yyt2 = cur; - goto yy307; + goto yy329; } - goto yy272; + goto yy273; } -yy294: +yy308: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'l') goto yy308; - if (yych == 'n') goto yy309; - goto yy272; -yy295: + if (yych == 'l') goto yy330; + if (yych == 'n') goto yy331; + goto yy273; +yy309: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'i') goto yy310; - goto yy272; -yy296: + if (yych == 'i') goto yy332; + goto yy273; +yy310: ++cur; yych = cur < lim ? *cur : -1; -yy297: - if (yych == ' ') goto yy296; - goto yy280; -yy298: +yy311: + if (yych == ' ') goto yy310; + goto yy286; +yy312: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'a') goto yy311; - goto yy272; -yy299: + if (yych == 'a') goto yy333; + goto yy273; +yy313: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'e') goto yy312; - goto yy272; -yy300: + if (yych == 'b') goto yy334; + goto yy273; +yy314: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'l') goto yy313; - if (yych == 'n') goto yy309; - goto yy272; -yy301: + if (yych <= ',') { + if (yych == ' ') goto yy314; + goto yy273; + } else { + if (yych <= '-') { + yyt1 = cur; + goto yy335; + } + if (yych <= '/') goto yy273; + if (yych <= '9') { + yyt1 = cur; + goto yy336; + } + goto yy273; + } +yy315: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'c') goto yy314; - if (yych == 't') goto yy315; - goto yy272; -yy302: + if (yych == 'e') goto yy337; + goto yy273; +yy316: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'a') goto yy316; - goto yy272; -yy303: + if (yych == 'l') goto yy338; + if (yych == 'n') goto yy331; + goto yy273; +yy317: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'g') goto yy317; - goto yy272; -yy304: + if (yych == 'c') goto yy339; + if (yych == 't') goto yy340; + goto yy273; +yy318: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'e') goto yy318; - goto yy272; -yy305: + if (yych == 'a') goto yy341; + goto yy273; +yy319: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'b') goto yy342; + goto yy273; +yy320: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'g') goto yy343; + goto yy273; +yy321: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'e') goto yy344; + goto yy273; +yy322: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == ' ') goto yy345; + goto yy273; +yy323: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'e') goto yy346; + goto yy273; +yy324: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == ' ') goto yy347; + goto yy273; +yy325: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'e') goto yy348; + goto yy273; +yy326: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'b') goto yy349; + goto yy273; +yy327: ++cur; yych = cur < lim ? *cur : -1; if (yych <= '/') { @@ -3567,20 +5922,20 @@ dvi_exec_pdf(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, curs yyt3 = NULL; yyt4 = NULL; yyt5 = NULL; - goto yy292; + goto yy306; } - if (yych <= '9') goto yy305; + if (yych <= '9') goto yy327; yyt2 = NULL; yyt3 = NULL; yyt4 = NULL; yyt5 = NULL; - goto yy292; -yy306: + goto yy306; +yy328: ++cur; yych = cur < lim ? *cur : -1; - if (yych <= '/') goto yy272; - if (yych >= ':') goto yy272; -yy307: + if (yych <= '/') goto yy273; + if (yych >= ':') goto yy273; +yy329: ++cur; yych = cur < lim ? *cur : -1; if (yych <= '.') { @@ -3588,98 +5943,193 @@ dvi_exec_pdf(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, curs yyt3 = NULL; yyt4 = NULL; yyt5 = NULL; - goto yy319; + goto yy350; } - if (yych <= '-') goto yy272; - goto yy320; + if (yych <= '-') goto yy273; + goto yy351; } else { if (yych <= '9') { - if (yych <= '/') goto yy272; - goto yy307; + if (yych <= '/') goto yy273; + goto yy329; } else { if (yych == ']') { yyt3 = NULL; yyt4 = NULL; yyt5 = NULL; - goto yy321; + goto yy352; } - goto yy272; + goto yy273; } } -yy308: +yy330: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'o') goto yy322; - goto yy272; -yy309: + if (yych == 'o') goto yy353; + goto yy273; +yy331: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 't') goto yy323; - goto yy272; -yy310: + if (yych == 't') goto yy354; + goto yy273; +yy332: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'n') goto yy324; - goto yy272; -yy311: + if (yych == 'n') goto yy355; + goto yy273; +yy333: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'n') goto yy325; - goto yy272; -yy312: + if (yych == 'n') goto yy356; + goto yy273; +yy334: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'j') goto yy357; + goto yy273; +yy335: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych <= '/') goto yy273; + if (yych >= ':') goto yy273; +yy336: ++cur; -#line 1204 "dvi_special.re2c.c" + yych = cur < lim ? *cur : -1; + if (yych <= '-') { + if (yych == ' ') goto yy358; + goto yy273; + } else { + if (yych <= '.') goto yy359; + if (yych <= '/') goto yy273; + if (yych <= '9') goto yy336; + goto yy273; + } +yy337: + ++cur; +#line 3438 "dvi_special.re2c.c" { return pdf_code(ctx, dc, st, cur, lim); } -#line 3634 "dvi_special.c" -yy313: +#line 6011 "dvi_special.c" +yy338: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'o') goto yy326; - goto yy272; -yy314: + if (yych == 'o') goto yy360; + goto yy273; +yy339: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'o') goto yy327; - goto yy272; -yy315: + if (yych == 'o') goto yy361; + goto yy273; +yy340: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'r') goto yy328; - goto yy272; -yy316: + if (yych == 'r') goto yy362; + goto yy273; +yy341: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'n') goto yy329; - goto yy272; -yy317: + if (yych == 'n') goto yy363; + goto yy273; +yy342: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'e') goto yy330; - goto yy272; -yy318: + if (yych == 'j') goto yy364; + goto yy273; +yy343: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 's') goto yy331; - goto yy272; -yy319: + if (yych == 'e') goto yy365; + goto yy273; +yy344: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'r') goto yy366; + goto yy273; +yy345: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == ' ') goto yy345; +#line 3492 "dvi_special.re2c.c" + { + // pdf:obj @name << dict >> — create named dictionary object (no-op for us) + return 1; + } +#line 6056 "dvi_special.c" +yy346: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 's') goto yy367; + goto yy273; +yy347: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == ' ') goto yy347; +#line 3498 "dvi_special.re2c.c" + { + // Parse: put @objname << /key1 @val1 /key2 @val2 ... >> + // We specifically look for: put @pgfpatterns << /pgfpatN @pgfpatternobjectN >> + while (cur < lim && (*cur == ' ' || *cur == '\t')) cur++; + const char *obj_start = cur; + while (cur < lim && *cur != ' ' && *cur != '\t' && *cur != '<') cur++; + int obj_len = (int)(cur - obj_start); + // Check if this is @pgfpatterns + if (obj_len >= 13 && memcmp(obj_start[0] == '@' ? obj_start+1 : obj_start, + "pgfpatterns", 11) == 0) { + while (cur < lim && (*cur == ' ' || *cur == '\t')) cur++; + if (cur + 1 < lim && cur[0] == '<' && cur[1] == '<') { + cur += 2; // skip << + // Parse key-value pairs: /pgfpatN @pgfpatternobjectN + while (cur < lim) { + while (cur < lim && (*cur == ' ' || *cur == '\t' || *cur == '\n')) cur++; + if (cur + 1 < lim && cur[0] == '>' && cur[1] == '>') break; + if (cur < lim && *cur == '/') { + cur++; // skip / + const char *pn = cur; + while (cur < lim && *cur != ' ' && *cur != '\t' && *cur != '>' && *cur != '/') cur++; + int pnl = (int)(cur - pn); + while (cur < lim && (*cur == ' ' || *cur == '\t')) cur++; + const char *on = cur; + while (cur < lim && *cur != ' ' && *cur != '\t' && *cur != '>' && *cur != '/') cur++; + int onl = (int)(cur - on); + if (pnl > 0 && onl > 0) { + pdf_pat_map_name(pn, pnl, on, onl); + } + } else { + cur++; + } + } + } + } + return 1; + } +#line 6104 "dvi_special.c" +yy348: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'a') goto yy368; + goto yy273; +yy349: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'j') goto yy369; + goto yy273; +yy350: ++cur; yych = cur < lim ? *cur : -1; if (yych <= '-') { - if (yych == ' ') goto yy319; - if (yych <= ',') goto yy272; + if (yych == ' ') goto yy350; + if (yych <= ',') goto yy273; yyt3 = cur; - goto yy332; + goto yy370; } else { if (yych <= '9') { - if (yych <= '/') goto yy272; + if (yych <= '/') goto yy273; yyt3 = cur; - goto yy333; + goto yy371; } else { - if (yych == ']') goto yy321; - goto yy272; + if (yych == ']') goto yy352; + goto yy273; } } -yy320: +yy351: ++cur; yych = cur < lim ? *cur : -1; if (yych <= '/') { @@ -3687,203 +6137,323 @@ dvi_exec_pdf(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, curs yyt3 = NULL; yyt4 = NULL; yyt5 = NULL; - goto yy319; + goto yy350; } - goto yy272; + goto yy273; } else { - if (yych <= '9') goto yy320; - if (yych != ']') goto yy272; + if (yych <= '9') goto yy351; + if (yych != ']') goto yy273; yyt3 = NULL; yyt4 = NULL; yyt5 = NULL; } -yy321: +yy352: ++cur; yyt1 = NULL; - goto yy292; -yy322: + goto yy306; +yy353: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'r') goto yy289; - goto yy272; -yy323: + if (yych == 'r') goto yy303; + goto yy273; +yy354: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'e') goto yy334; - goto yy272; -yy324: + if (yych == 'e') goto yy372; + goto yy273; +yy355: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'c') goto yy335; - if (yych == 't') goto yy336; - goto yy272; -yy325: + if (yych == 'c') goto yy373; + if (yych == 't') goto yy374; + goto yy273; +yy356: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 's') goto yy296; - goto yy272; -yy326: + if (yych == 's') goto yy310; + goto yy273; +yy357: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'r') goto yy337; - goto yy272; -yy327: + if (yych == ' ') goto yy375; + goto yy273; +yy358: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'l') goto yy313; - goto yy272; -yy328: + if (yych <= ',') { + if (yych == ' ') goto yy358; + goto yy273; + } else { + if (yych <= '-') { + yyt2 = cur; + goto yy376; + } + if (yych <= '/') goto yy273; + if (yych <= '9') { + yyt2 = cur; + goto yy377; + } + goto yy273; + } +yy359: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'a') goto yy338; - goto yy272; -yy329: + if (yych == ' ') goto yy358; + if (yych <= '/') goto yy273; + if (yych <= '9') goto yy359; + goto yy273; +yy360: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'r') goto yy378; + goto yy273; +yy361: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'l') goto yy338; + goto yy273; +yy362: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'a') goto yy379; + goto yy273; +yy363: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 's') goto yy380; + goto yy273; +yy364: + ++cur; + yych = cur < lim ? *cur : -1; + if (yybm[0+yych] & 64) { + goto yy364; + } +#line 3537 "dvi_special.re2c.c" + { return 1; } +#line 6234 "dvi_special.c" +yy365: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == ' ') goto yy381; + goto yy273; +yy366: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'a') goto yy382; + goto yy273; +yy367: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 's') goto yy339; - goto yy272; -yy330: + if (yych == 'i') goto yy383; + goto yy273; +yy368: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ' ') goto yy340; - goto yy272; -yy331: + if (yych == 'm') goto yy384; + goto yy273; +yy369: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'i') goto yy341; - goto yy272; -yy332: + if (yych == ' ') goto yy385; + goto yy273; +yy370: ++cur; yych = cur < lim ? *cur : -1; - if (yych <= '/') goto yy272; - if (yych >= ':') goto yy272; -yy333: + if (yych <= '/') goto yy273; + if (yych >= ':') goto yy273; +yy371: ++cur; yych = cur < lim ? *cur : -1; if (yych <= '-') { - if (yych == ' ') goto yy342; - goto yy272; + if (yych == ' ') goto yy386; + goto yy273; } else { - if (yych <= '.') goto yy343; - if (yych <= '/') goto yy272; - if (yych <= '9') goto yy333; - goto yy272; + if (yych <= '.') goto yy387; + if (yych <= '/') goto yy273; + if (yych <= '9') goto yy371; + goto yy273; } -yy334: +yy372: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'n') goto yy344; - goto yy272; -yy335: + if (yych == 'n') goto yy388; + goto yy273; +yy373: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'o') goto yy345; - goto yy272; -yy336: + if (yych == 'o') goto yy389; + goto yy273; +yy374: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'r') goto yy346; - goto yy272; -yy337: + if (yych == 'r') goto yy390; + goto yy273; +yy375: ++cur; - goto yy283; -yy338: + yych = cur < lim ? *cur : -1; + if (yych == ' ') goto yy375; +#line 3536 "dvi_special.re2c.c" + { return 1; } +#line 6298 "dvi_special.c" +yy376: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'n') goto yy347; - goto yy272; -yy339: + if (yych <= '/') goto yy273; + if (yych >= ':') goto yy273; +yy377: ++cur; - goto yy286; -yy340: + yych = cur < lim ? *cur : -1; + if (yych <= '-') { + if (yych == ' ') goto yy391; + goto yy273; + } else { + if (yych <= '.') goto yy392; + if (yych <= '/') goto yy273; + if (yych <= '9') goto yy377; + goto yy273; + } +yy378: + ++cur; + goto yy291; +yy379: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'n') goto yy393; + goto yy273; +yy380: + ++cur; + goto yy294; +yy381: ++cur; yych = cur < lim ? *cur : -1; if (yych <= ',') { if (yych <= ' ') { - if (yych <= 0x1F) goto yy272; - goto yy340; + if (yych <= 0x1F) goto yy273; + goto yy381; } else { if (yych == '(') { yyt2 = cur; - goto yy348; + goto yy394; } - goto yy272; + goto yy273; } } else { if (yych <= '9') { if (yych <= '-') { yyt2 = cur; - goto yy349; + goto yy395; } - if (yych <= '/') goto yy272; + if (yych <= '/') goto yy273; yyt2 = cur; - goto yy350; + goto yy396; } else { - if (yych <= '`') goto yy272; + if (yych <= '`') goto yy273; if (yych <= 'z') { yyt2 = cur; - goto yy351; + goto yy397; } - goto yy272; + goto yy273; } } -yy341: +yy382: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'z') goto yy352; - goto yy272; -yy342: + if (yych == 'l') goto yy398; + goto yy273; +yy383: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'z') goto yy399; + goto yy273; +yy384: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == ' ') goto yy400; + goto yy273; +yy385: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == ' ') goto yy385; +#line 3538 "dvi_special.re2c.c" + { return 1; } +#line 6380 "dvi_special.c" +yy386: ++cur; yych = cur < lim ? *cur : -1; if (yych <= ',') { - if (yych == ' ') goto yy342; - goto yy272; + if (yych == ' ') goto yy386; + goto yy273; } else { if (yych <= '-') { yyt4 = cur; - goto yy353; + goto yy401; } - if (yych <= '/') goto yy272; + if (yych <= '/') goto yy273; if (yych <= '9') { yyt4 = cur; - goto yy354; + goto yy402; } - goto yy272; + goto yy273; } -yy343: +yy387: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ' ') goto yy342; - if (yych <= '/') goto yy272; - if (yych <= '9') goto yy343; - goto yy272; -yy344: + if (yych == ' ') goto yy386; + if (yych <= '/') goto yy273; + if (yych <= '9') goto yy387; + goto yy273; +yy388: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 't') goto yy355; - goto yy272; -yy345: + if (yych == 't') goto yy403; + goto yy273; +yy389: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'l') goto yy308; - goto yy272; -yy346: + if (yych == 'l') goto yy330; + goto yy273; +yy390: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'a') goto yy356; - goto yy272; -yy347: + if (yych == 'a') goto yy404; + goto yy273; +yy391: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 's') goto yy357; - goto yy272; -yy348: + if (yych <= ',') { + if (yych == ' ') goto yy391; + goto yy273; + } else { + if (yych <= '-') { + yyt3 = cur; + goto yy405; + } + if (yych <= '/') goto yy273; + if (yych <= '9') { + yyt3 = cur; + goto yy406; + } + goto yy273; + } +yy392: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == ' ') goto yy391; + if (yych <= '/') goto yy273; + if (yych <= '9') goto yy392; + goto yy273; +yy393: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 's') goto yy407; + goto yy273; +yy394: ++cur; f0 = yyt1; pxform = yyt2; pstart = cur; pstart += -1; -#line 1153 "dvi_special.re2c.c" +#line 3341 "dvi_special.re2c.c" { struct xform_spec xf = xform_spec(); pxform = parse_xform_or_dim(&xf, pxform, pstart); @@ -3900,543 +6470,812 @@ dvi_exec_pdf(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, curs else return 1; } -#line 3904 "dvi_special.c" -yy349: +#line 6474 "dvi_special.c" +yy395: ++cur; yych = cur < lim ? *cur : -1; - if (yych <= '/') goto yy272; - if (yych >= ':') goto yy272; -yy350: + if (yych <= '/') goto yy273; + if (yych >= ':') goto yy273; +yy396: ++cur; yych = cur < lim ? *cur : -1; if (yych <= '-') { if (yych <= '\'') { - if (yych != ' ') goto yy272; + if (yych != ' ') goto yy273; } else { - if (yych <= '(') goto yy348; - if (yych <= ',') goto yy272; - goto yy349; + if (yych <= '(') goto yy394; + if (yych <= ',') goto yy273; + goto yy395; } } else { if (yych <= '9') { - if (yych <= '.') goto yy351; - if (yych <= '/') goto yy272; - goto yy350; + if (yych <= '.') goto yy397; + if (yych <= '/') goto yy273; + goto yy396; } else { - if (yych <= '`') goto yy272; - if (yych >= '{') goto yy272; + if (yych <= '`') goto yy273; + if (yych >= '{') goto yy273; } } -yy351: +yy397: ++cur; yych = cur < lim ? *cur : -1; if (yybm[0+yych] & 128) { - goto yy351; + goto yy397; } if (yych <= ',') { - if (yych == '(') goto yy348; - goto yy272; + if (yych == '(') goto yy394; + goto yy273; } else { - if (yych <= '-') goto yy349; - if (yych <= '/') goto yy272; - if (yych <= '9') goto yy350; - goto yy272; + if (yych <= '-') goto yy395; + if (yych <= '/') goto yy273; + if (yych <= '9') goto yy396; + goto yy273; } -yy352: +yy398: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'e') goto yy358; - goto yy272; -yy353: + if (yych == ' ') goto yy408; + goto yy273; +yy399: ++cur; yych = cur < lim ? *cur : -1; - if (yych <= '/') goto yy272; - if (yych >= ':') goto yy272; -yy354: + if (yych == 'e') goto yy410; + goto yy273; +yy400: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == ' ') goto yy400; +#line 3444 "dvi_special.re2c.c" + { + // Parse: stream @name (content) << dict >> + // Skip whitespace + while (cur < lim && (*cur == ' ' || *cur == '\t')) cur++; + // Read object name + const char *obj_start = cur; + while (cur < lim && *cur != ' ' && *cur != '\t' && *cur != '(') cur++; + int obj_len = (int)(cur - obj_start); + while (cur < lim && (*cur == ' ' || *cur == '\t')) cur++; + // Read content in parentheses: (...) + const char *content = NULL; + int content_len = 0; + if (cur < lim && *cur == '(') { + cur++; // skip ( + content = cur; + int depth = 1; + while (cur < lim && depth > 0) { + if (*cur == '(' && (cur == content || *(cur-1) != '\\')) depth++; + else if (*cur == ')' && (cur == content || *(cur-1) != '\\')) depth--; + if (depth > 0) cur++; + } + content_len = (int)(cur - content); + if (cur < lim) cur++; // skip closing ) + } + while (cur < lim && (*cur == ' ' || *cur == '\t')) cur++; + // Read dictionary in << ... >> + const char *dict = NULL; + int dict_len = 0; + if (cur + 1 < lim && cur[0] == '<' && cur[1] == '<') { + cur += 2; // skip << + dict = cur; + int dd = 1; + while (cur + 1 < lim && dd > 0) { + if (cur[0] == '<' && cur[1] == '<') { dd++; cur += 2; } + else if (cur[0] == '>' && cur[1] == '>') { dd--; if (dd > 0) cur += 2; } + else cur++; + } + dict_len = (int)(cur - dict); + } + // Store pattern definition if it's a pattern object + if (content && obj_len > 0) { + pdf_pat_define(obj_start, obj_len, content, content_len, + dict ? dict : "", dict_len); + } + return 1; + } +#line 6577 "dvi_special.c" +yy401: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych <= '/') goto yy273; + if (yych >= ':') goto yy273; +yy402: ++cur; yych = cur < lim ? *cur : -1; if (yych <= '.') { if (yych == ' ') { yyt5 = NULL; - goto yy359; + goto yy411; } - if (yych <= '-') goto yy272; - goto yy360; + if (yych <= '-') goto yy273; + goto yy412; } else { if (yych <= '9') { - if (yych <= '/') goto yy272; - goto yy354; + if (yych <= '/') goto yy273; + goto yy402; } else { if (yych == ']') { yyt5 = NULL; - goto yy321; + goto yy352; } - goto yy272; + goto yy273; } } -yy355: +yy403: ++cur; f0 = cur; f0 += -8; f1 = cur; -#line 1177 "dvi_special.re2c.c" +#line 3365 "dvi_special.re2c.c" { + // bcontent/econtent: these delimit content streams but the tikz + // engine also emits explicit q/Q and btrans/etrans for state + // management. Treating these as gsave/grestore would double-push + // and cause the text to lose the btrans CTM. if (f1 != lim) fprintf(stderr, "unhandled pdf content: %.*s\n", (int)(lim - f0), f0); return 1; } -#line 3990 "dvi_special.c" -yy356: +#line 6621 "dvi_special.c" +yy404: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'n') goto yy361; - goto yy272; -yy357: + if (yych == 'n') goto yy413; + goto yy273; +yy405: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'f') goto yy362; - goto yy272; -yy358: + if (yych <= '/') goto yy273; + if (yych >= ':') goto yy273; +yy406: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ' ') goto yy363; - goto yy272; -yy359: + if (yych <= '-') { + if (yych == ' ') goto yy414; + goto yy273; + } else { + if (yych <= '.') goto yy415; + if (yych <= '/') goto yy273; + if (yych <= '9') goto yy406; + goto yy273; + } +yy407: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'f') goto yy416; + goto yy273; +yy408: + yyaccept = 4; + ++cur; + mar = cur; + yych = cur < lim ? *cur : -1; + if (yych == ' ') goto yy408; + if (yych == 'd') goto yy417; +yy409: +#line 3441 "dvi_special.re2c.c" + { return pdf_code(ctx, dc, st, cur, lim); } +#line 6659 "dvi_special.c" +yy410: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == ' ') goto yy418; + goto yy273; +yy411: ++cur; yych = cur < lim ? *cur : -1; if (yych <= '-') { - if (yych == ' ') goto yy359; - if (yych <= ',') goto yy272; + if (yych == ' ') goto yy411; + if (yych <= ',') goto yy273; yyt5 = cur; - goto yy364; + goto yy419; } else { if (yych <= '9') { - if (yych <= '/') goto yy272; + if (yych <= '/') goto yy273; yyt5 = cur; - goto yy365; + goto yy420; } else { - if (yych == ']') goto yy321; - goto yy272; + if (yych == ']') goto yy352; + goto yy273; } } -yy360: +yy412: ++cur; yych = cur < lim ? *cur : -1; if (yych <= '/') { if (yych == ' ') { yyt5 = NULL; - goto yy359; + goto yy411; } - goto yy272; + goto yy273; } else { - if (yych <= '9') goto yy360; + if (yych <= '9') goto yy412; if (yych == ']') { yyt5 = NULL; - goto yy321; + goto yy352; } - goto yy272; + goto yy273; } -yy361: +yy413: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 's') goto yy366; - goto yy272; -yy362: + if (yych == 's') goto yy421; + goto yy273; +yy414: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'o') goto yy367; - goto yy272; -yy363: + if (yych <= ',') { + if (yych == ' ') goto yy414; + goto yy273; + } else { + if (yych <= '-') { + yyt4 = cur; + goto yy422; + } + if (yych <= '/') goto yy273; + if (yych <= '9') { + yyt4 = cur; + goto yy423; + } + goto yy273; + } +yy415: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == ' ') goto yy414; + if (yych <= '/') goto yy273; + if (yych <= '9') goto yy415; + goto yy273; +yy416: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'o') goto yy424; + goto yy273; +yy417: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'i') goto yy425; + goto yy273; +yy418: ++cur; yych = cur < lim ? *cur : -1; if (yych <= 'c') { - if (yych == ' ') goto yy363; - goto yy272; + if (yych == ' ') goto yy418; + goto yy273; } else { - if (yych <= 'd') goto yy368; - if (yych == 'w') goto yy369; - goto yy272; + if (yych <= 'd') goto yy426; + if (yych == 'w') goto yy427; + goto yy273; } -yy364: +yy419: ++cur; yych = cur < lim ? *cur : -1; - if (yych <= '/') goto yy272; - if (yych >= ':') goto yy272; -yy365: + if (yych <= '/') goto yy273; + if (yych >= ':') goto yy273; +yy420: ++cur; yych = cur < lim ? *cur : -1; if (yych <= '.') { - if (yych == ' ') goto yy370; - if (yych <= '-') goto yy272; - goto yy371; + if (yych == ' ') goto yy428; + if (yych <= '-') goto yy273; + goto yy429; } else { if (yych <= '9') { - if (yych <= '/') goto yy272; - goto yy365; + if (yych <= '/') goto yy273; + goto yy420; } else { - if (yych == ']') goto yy321; - goto yy272; + if (yych == ']') goto yy352; + goto yy273; } } -yy366: +yy421: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'f') goto yy372; - goto yy272; -yy367: + if (yych == 'f') goto yy430; + goto yy273; +yy422: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'r') goto yy373; - goto yy272; -yy368: + if (yych <= '/') goto yy273; + if (yych >= ':') goto yy273; +yy423: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'e') goto yy374; - goto yy272; -yy369: + if (yych <= '-') { + if (yych == ' ') goto yy431; + goto yy273; + } else { + if (yych <= '.') goto yy432; + if (yych <= '/') goto yy273; + if (yych <= '9') goto yy423; + goto yy273; + } +yy424: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'i') goto yy375; - goto yy272; -yy370: + if (yych == 'r') goto yy433; + goto yy273; +yy425: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ' ') goto yy370; - if (yych == ']') goto yy321; - goto yy272; -yy371: + if (yych == 'r') goto yy434; + goto yy273; +yy426: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'e') goto yy435; + goto yy273; +yy427: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'i') goto yy436; + goto yy273; +yy428: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == ' ') goto yy428; + if (yych == ']') goto yy352; + goto yy273; +yy429: ++cur; yych = cur < lim ? *cur : -1; if (yych <= '/') { - if (yych == ' ') goto yy370; - goto yy272; + if (yych == ' ') goto yy428; + goto yy273; } else { - if (yych <= '9') goto yy371; - if (yych == ']') goto yy321; - goto yy272; + if (yych <= '9') goto yy429; + if (yych == ']') goto yy352; + goto yy273; } -yy372: +yy430: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'o') goto yy376; - goto yy272; -yy373: + if (yych == 'o') goto yy437; + goto yy273; +yy431: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych <= ',') { + if (yych == ' ') goto yy431; + goto yy273; + } else { + if (yych <= '-') { + yyt5 = cur; + goto yy438; + } + if (yych <= '/') goto yy273; + if (yych <= '9') { + yyt5 = cur; + goto yy439; + } + goto yy273; + } +yy432: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == ' ') goto yy431; + if (yych <= '/') goto yy273; + if (yych <= '9') goto yy432; + goto yy273; +yy433: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'm') goto yy380; + goto yy273; +yy434: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'e') goto yy440; + goto yy273; +yy435: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'f') goto yy441; + goto yy273; +yy436: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'd') goto yy442; + goto yy273; +yy437: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'r') goto yy443; + goto yy273; +yy438: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych <= '/') goto yy273; + if (yych >= ':') goto yy273; +yy439: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych <= '-') { + if (yych == ' ') goto yy444; + goto yy273; + } else { + if (yych <= '.') goto yy445; + if (yych <= '/') goto yy273; + if (yych <= '9') goto yy439; + goto yy273; + } +yy440: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'c') goto yy446; + goto yy273; +yy441: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'a') goto yy447; + goto yy273; +yy442: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 't') goto yy448; + goto yy273; +yy443: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'm') goto yy310; + goto yy273; +yy444: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych <= ',') { + if (yych == ' ') goto yy444; + goto yy273; + } else { + if (yych <= '-') { + yyt6 = cur; + goto yy449; + } + if (yych <= '/') goto yy273; + if (yych <= '9') { + yyt6 = cur; + goto yy450; + } + goto yy273; + } +yy445: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'm') goto yy339; - goto yy272; -yy374: + if (yych == ' ') goto yy444; + if (yych <= '/') goto yy273; + if (yych <= '9') goto yy445; + goto yy273; +yy446: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'f') goto yy377; - goto yy272; -yy375: + if (yych == 't') goto yy452; + goto yy273; +yy447: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'd') goto yy378; - goto yy272; -yy376: + if (yych == 'u') goto yy453; + goto yy273; +yy448: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'r') goto yy379; - goto yy272; -yy377: + if (yych == 'h') goto yy454; + goto yy273; +yy449: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'a') goto yy380; - goto yy272; -yy378: + if (yych <= '/') goto yy273; + if (yych >= ':') goto yy273; +yy450: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 't') goto yy381; - goto yy272; -yy379: + if (yych == '.') goto yy455; + if (yych <= '/') goto yy451; + if (yych <= '9') goto yy450; +yy451: + f0 = yyt1; + f1 = yyt2; + f2 = yyt3; + f3 = yyt4; + f4 = yyt5; + f5 = yyt6; +#line 3426 "dvi_special.re2c.c" + { + // Standalone cm operator (used by PGF for text positioning in nodes) + fz_matrix mat; + mat.a = pfloat(f0, lim); mat.b = pfloat(f1, lim); + mat.c = pfloat(f2, lim); mat.d = pfloat(f3, lim); + mat.e = pfloat(f4, lim); mat.f = pfloat(f5, lim); + fz_matrix ctm = fz_concat(mat, dvi_get_ctm(dc, st)); + dvi_set_ctm(st, ctm); + return 1; + } +#line 6992 "dvi_special.c" +yy452: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'm') goto yy296; - goto yy272; -yy380: + if (yych == ' ') goto yy456; + goto yy273; +yy453: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'u') goto yy382; - goto yy272; -yy381: + if (yych == 'l') goto yy457; + goto yy273; +yy454: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'h') goto yy383; - goto yy272; -yy382: + if (yych == ' ') goto yy458; + goto yy273; +yy455: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'l') goto yy384; - goto yy272; -yy383: + if (yych <= '/') goto yy451; + if (yych <= '9') goto yy455; + goto yy451; +yy456: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ' ') goto yy385; - goto yy272; -yy384: + if (yych == ' ') goto yy456; + goto yy409; +yy457: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 't') goto yy386; - goto yy272; -yy385: + if (yych == 't') goto yy459; + goto yy273; +yy458: ++cur; yych = cur < lim ? *cur : -1; if (yych <= ',') { - if (yych == ' ') goto yy385; - goto yy272; + if (yych == ' ') goto yy458; + goto yy273; } else { if (yych <= '-') { yyt1 = cur; - goto yy387; + goto yy460; } - if (yych <= '/') goto yy272; + if (yych <= '/') goto yy273; if (yych <= '9') { yyt1 = cur; - goto yy388; + goto yy461; } - goto yy272; + goto yy273; } -yy386: +yy459: ++cur; -#line 1150 "dvi_special.re2c.c" +#line 3338 "dvi_special.re2c.c" { return 1; } -#line 4207 "dvi_special.c" -yy387: +#line 7046 "dvi_special.c" +yy460: ++cur; yych = cur < lim ? *cur : -1; - if (yych <= '/') goto yy272; - if (yych >= ':') goto yy272; -yy388: + if (yych <= '/') goto yy273; + if (yych >= ':') goto yy273; +yy461: ++cur; yych = cur < lim ? *cur : -1; if (yych <= 'h') { if (yych <= '/') { - if (yych != '.') goto yy272; + if (yych != '.') goto yy273; } else { - if (yych <= '9') goto yy388; - if (yych == 'c') goto yy390; - goto yy272; + if (yych <= '9') goto yy461; + if (yych == 'c') goto yy463; + goto yy273; } } else { if (yych <= 'o') { - if (yych <= 'i') goto yy391; - if (yych == 'm') goto yy390; - goto yy272; + if (yych <= 'i') goto yy464; + if (yych == 'm') goto yy463; + goto yy273; } else { - if (yych <= 'p') goto yy392; - if (yych == 't') goto yy393; - goto yy272; + if (yych <= 'p') goto yy465; + if (yych == 't') goto yy466; + goto yy273; } } -yy389: +yy462: ++cur; yych = cur < lim ? *cur : -1; if (yych <= 'i') { if (yych <= 'b') { - if (yych <= '/') goto yy272; - if (yych <= '9') goto yy389; - goto yy272; + if (yych <= '/') goto yy273; + if (yych <= '9') goto yy462; + goto yy273; } else { - if (yych <= 'c') goto yy390; - if (yych <= 'h') goto yy272; - goto yy391; + if (yych <= 'c') goto yy463; + if (yych <= 'h') goto yy273; + goto yy464; } } else { if (yych <= 'o') { - if (yych != 'm') goto yy272; + if (yych != 'm') goto yy273; } else { - if (yych <= 'p') goto yy392; - if (yych == 't') goto yy393; - goto yy272; + if (yych <= 'p') goto yy465; + if (yych == 't') goto yy466; + goto yy273; } } -yy390: +yy463: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'm') goto yy394; - goto yy272; -yy391: + if (yych == 'm') goto yy467; + goto yy273; +yy464: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'n') goto yy394; - goto yy272; -yy392: + if (yych == 'n') goto yy467; + goto yy273; +yy465: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 't') goto yy394; - goto yy272; -yy393: + if (yych == 't') goto yy467; + goto yy273; +yy466: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'r') goto yy395; - goto yy272; -yy394: + if (yych == 'r') goto yy468; + goto yy273; +yy467: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ' ') goto yy396; - goto yy272; -yy395: + if (yych == ' ') goto yy469; + goto yy273; +yy468: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'u') goto yy397; - goto yy272; -yy396: + if (yych == 'u') goto yy470; + goto yy273; +yy469: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ' ') goto yy396; - if (yych == 'h') goto yy398; - goto yy272; -yy397: + if (yych == ' ') goto yy469; + if (yych == 'h') goto yy471; + goto yy273; +yy470: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'e') goto yy399; - goto yy272; -yy398: + if (yych == 'e') goto yy472; + goto yy273; +yy471: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'e') goto yy400; - goto yy272; -yy399: + if (yych == 'e') goto yy473; + goto yy273; +yy472: ++cur; yych = cur < lim ? *cur : -1; switch (yych) { case 'c': - case 'm': goto yy390; - case 'i': goto yy391; - case 'p': goto yy392; - default: goto yy272; + case 'm': goto yy463; + case 'i': goto yy464; + case 'p': goto yy465; + default: goto yy273; } -yy400: +yy473: ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'i') goto yy272; + if (yych != 'i') goto yy273; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'g') goto yy272; + if (yych != 'g') goto yy273; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'h') goto yy272; + if (yych != 'h') goto yy273; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 't') goto yy272; + if (yych != 't') goto yy273; ++cur; yych = cur < lim ? *cur : -1; - if (yych != ' ') goto yy272; -yy401: + if (yych != ' ') goto yy273; +yy474: ++cur; yych = cur < lim ? *cur : -1; if (yych <= ',') { - if (yych == ' ') goto yy401; - goto yy272; + if (yych == ' ') goto yy474; + goto yy273; } else { if (yych <= '-') { yyt2 = cur; - goto yy402; + goto yy475; } - if (yych <= '/') goto yy272; + if (yych <= '/') goto yy273; if (yych <= '9') { yyt2 = cur; - goto yy403; + goto yy476; } - goto yy272; + goto yy273; } -yy402: +yy475: ++cur; yych = cur < lim ? *cur : -1; - if (yych <= '/') goto yy272; - if (yych >= ':') goto yy272; -yy403: + if (yych <= '/') goto yy273; + if (yych >= ':') goto yy273; +yy476: ++cur; yych = cur < lim ? *cur : -1; if (yych <= 'h') { if (yych <= '/') { - if (yych != '.') goto yy272; + if (yych != '.') goto yy273; } else { - if (yych <= '9') goto yy403; - if (yych == 'c') goto yy405; - goto yy272; + if (yych <= '9') goto yy476; + if (yych == 'c') goto yy478; + goto yy273; } } else { if (yych <= 'o') { - if (yych <= 'i') goto yy406; - if (yych == 'm') goto yy405; - goto yy272; + if (yych <= 'i') goto yy479; + if (yych == 'm') goto yy478; + goto yy273; } else { - if (yych <= 'p') goto yy407; - if (yych == 't') goto yy408; - goto yy272; + if (yych <= 'p') goto yy480; + if (yych == 't') goto yy481; + goto yy273; } } -yy404: +yy477: ++cur; yych = cur < lim ? *cur : -1; if (yych <= 'i') { if (yych <= 'b') { - if (yych <= '/') goto yy272; - if (yych <= '9') goto yy404; - goto yy272; + if (yych <= '/') goto yy273; + if (yych <= '9') goto yy477; + goto yy273; } else { - if (yych <= 'c') goto yy405; - if (yych <= 'h') goto yy272; - goto yy406; + if (yych <= 'c') goto yy478; + if (yych <= 'h') goto yy273; + goto yy479; } } else { if (yych <= 'o') { - if (yych != 'm') goto yy272; + if (yych != 'm') goto yy273; } else { - if (yych <= 'p') goto yy407; - if (yych == 't') goto yy408; - goto yy272; + if (yych <= 'p') goto yy480; + if (yych == 't') goto yy481; + goto yy273; } } -yy405: +yy478: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'm') goto yy409; - goto yy272; -yy406: + if (yych == 'm') goto yy482; + goto yy273; +yy479: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'n') goto yy409; - goto yy272; -yy407: + if (yych == 'n') goto yy482; + goto yy273; +yy480: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 't') goto yy409; - goto yy272; -yy408: + if (yych == 't') goto yy482; + goto yy273; +yy481: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'r') goto yy410; - goto yy272; -yy409: + if (yych == 'r') goto yy483; + goto yy273; +yy482: ++cur; f0 = yyt1; f1 = yyt2; -#line 1147 "dvi_special.re2c.c" +#line 3335 "dvi_special.re2c.c" { return 1; } -#line 4422 "dvi_special.c" -yy410: +#line 7261 "dvi_special.c" +yy483: ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'u') goto yy272; + if (yych != 'u') goto yy273; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'e') goto yy272; + if (yych != 'e') goto yy273; ++cur; yych = cur < lim ? *cur : -1; switch (yych) { case 'c': - case 'm': goto yy405; - case 'i': goto yy406; - case 'p': goto yy407; - default: goto yy272; + case 'm': goto yy478; + case 'i': goto yy479; + case 'p': goto yy480; + default: goto yy273; } } -#line 1209 "dvi_special.re2c.c" +#line 3543 "dvi_special.re2c.c" } @@ -4444,491 +7283,853 @@ bool dvi_exec_special(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t { cursor_t mar, i, j; - // fprintf(stderr, "special: %.*s\n", (int)(lim - cur), cur); - for (;;) { - -#line 4453 "dvi_special.c" + +#line 7290 "dvi_special.c" { int yych; + unsigned int yyaccept = 0; static const unsigned char yybm[] = { - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 64, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 64, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, }; mar = cur; yych = cur < lim ? *cur : -1; if (yych <= 'k') { - if (yych <= ' ') { - if (yych >= ' ') goto yy413; + if (yych <= '!') { + if (yych <= 0x1F) goto yy485; + if (yych <= ' ') goto yy486; + goto yy487; } else { - if (yych == 'c') goto yy414; + if (yych == 'c') goto yy489; } } else { if (yych <= 'p') { - if (yych <= 'l') goto yy416; - if (yych >= 'p') goto yy417; + if (yych <= 'l') goto yy491; + if (yych >= 'p') goto yy492; } else { - if (yych == 'x') goto yy418; + if (yych == 'x') goto yy493; } } -yy412: -#line 1273 "dvi_special.re2c.c" +yy485: +#line 3648 "dvi_special.re2c.c" { return unhandled("special", cur, lim, 0); } -#line 4509 "dvi_special.c" -yy413: +#line 7349 "dvi_special.c" +yy486: ++cur; -#line 1223 "dvi_special.re2c.c" +#line 3555 "dvi_special.re2c.c" { continue; } -#line 4514 "dvi_special.c" -yy414: +#line 7354 "dvi_special.c" +yy487: + yyaccept = 1; ++cur; + mar = cur; yych = cur < lim ? *cur : -1; - if (yych == 'o') goto yy419; -yy415: + if (yybm[0+yych] & 64) { + goto yy487; + } + if (yych == '/') goto yy494; +yy488: +#line 3625 "dvi_special.re2c.c" + { + // General "!" specials: scan for any function definitions. + // Pattern declarations come as: save true setglobal globaldict begin + // /pgfpatN { ... } bind def end restore + // These don't start with /pgf so the specific handler above misses them. + // Scan for /pgfpat in the content to route to pattern table. + ps_parse_defs(cur, lim); + return 1; + } +#line 7375 "dvi_special.c" +yy489: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'o') goto yy495; +yy490: cur = mar; - goto yy412; -yy416: + if (yyaccept == 0) { + goto yy485; + } else { + goto yy488; + } +yy491: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'a') goto yy420; - goto yy415; -yy417: + if (yych == 'a') goto yy496; + goto yy490; +yy492: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'd') goto yy421; - goto yy415; -yy418: + if (yych <= 'c') { + if (yych == 'a') goto yy497; + goto yy490; + } else { + if (yych <= 'd') goto yy498; + if (yych == 's') goto yy499; + goto yy490; + } +yy493: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ':') goto yy422; - goto yy415; -yy419: + if (yych == ':') goto yy500; + goto yy490; +yy494: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'p') goto yy501; + goto yy490; +yy495: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'l') goto yy502; + goto yy490; +yy496: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'n') goto yy503; + goto yy490; +yy497: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'p') goto yy504; + goto yy490; +yy498: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'f') goto yy505; + goto yy490; +yy499: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == ':') goto yy506; + goto yy490; +yy500: + ++cur; +#line 3592 "dvi_special.re2c.c" + { + struct xform_spec xf = xform_spec(); + cur = parse_xform_or_dim(&xf, cur, lim); + st->gs.ctm = fz_concat(xf.ctm, st->gs.ctm); + if (cur < lim) + return unhandled("pdf x", cur, lim, 0); + return 1; + } +#line 7449 "dvi_special.c" +yy501: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'g') goto yy508; + goto yy490; +yy502: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'o') goto yy509; + goto yy490; +yy503: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'd') goto yy510; + goto yy490; +yy504: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'e') goto yy511; + goto yy490; +yy505: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == ':') goto yy512; + if (yych == 'c') goto yy513; + goto yy490; +yy506: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == ':') goto yy516; + goto yy515; +yy507: +#line 3636 "dvi_special.re2c.c" + { + // ps: (single colon) — state management: pgfsc/pgffc clear, etc. + // Process same as ps:: so that pgfsc{}/pgffc{} clears take effect. + return ps_code(ctx, dc, st, cur, lim); + } +#line 7488 "dvi_special.c" +yy508: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'f') goto yy517; + goto yy490; +yy509: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'r') goto yy518; + goto yy490; +yy510: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 's') goto yy519; + goto yy490; +yy511: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'r') goto yy520; + goto yy490; +yy512: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == ' ') goto yy512; +#line 3602 "dvi_special.re2c.c" + { return dvi_exec_pdf(ctx, dc, st, cur, lim); } +#line 7515 "dvi_special.c" +yy513: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'o') goto yy521; + goto yy490; +yy514: + ++cur; + yych = cur < lim ? *cur : -1; +yy515: + if (yych == ' ') goto yy514; + goto yy507; +yy516: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == ' ') goto yy516; +#line 3643 "dvi_special.re2c.c" + { + return ps_code(ctx, dc, st, cur, lim); + } +#line 7535 "dvi_special.c" +yy517: + ++cur; +#line 3612 "dvi_special.re2c.c" + { + // PGF "!" specials: always parse function definitions. + // IMPORTANT: the match consumed "/pgf" from the content (e.g. from + // "/pgfsc"), so back up 4 chars so ps_parse_defs sees the full + // function name including the /pgf prefix. + cursor_t content_start = cur - 4; // include "/pgf" that was consumed + ps_parse_defs(content_start, lim); + if (dc->dev) + return dvi_exec_pgf_shading(ctx, dc, st, cur, lim); + return 1; + } +#line 7550 "dvi_special.c" +yy518: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == ' ') goto yy522; + goto yy490; +yy519: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'c') goto yy523; + goto yy490; +yy520: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 's') goto yy524; + goto yy490; +yy521: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'l') goto yy525; + goto yy490; +yy522: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == ' ') goto yy522; + if (yych == 'p') goto yy526; + goto yy490; +yy523: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'a') goto yy527; + goto yy490; +yy524: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'i') goto yy528; + goto yy490; +yy525: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'o') goto yy529; + goto yy490; +yy526: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'o') goto yy530; + if (yych == 'u') goto yy531; + goto yy490; +yy527: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'p') goto yy532; + goto yy490; +yy528: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'z') goto yy533; + goto yy490; +yy529: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'r') goto yy534; + goto yy490; +yy530: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'p') goto yy535; + goto yy490; +yy531: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 's') goto yy536; + goto yy490; +yy532: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'l') goto yy423; - goto yy415; -yy420: + if (yych == 'e') goto yy537; + goto yy490; +yy533: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'n') goto yy424; - goto yy415; -yy421: + if (yych == 'e') goto yy538; + goto yy490; +yy534: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'f') goto yy425; - goto yy415; -yy422: + if (yych == 's') goto yy539; + goto yy490; +yy535: ++cur; -#line 1260 "dvi_special.re2c.c" - { - struct xform_spec xf = xform_spec(); - cur = parse_xform_or_dim(&xf, cur, lim); - st->gs.ctm = fz_concat(xf.ctm, st->gs.ctm); - if (cur < lim) - return unhandled("pdf x", cur, lim, 0); - return 1; - } -#line 4563 "dvi_special.c" -yy423: +#line 3583 "dvi_special.re2c.c" + { return colorstack_pop(ctx, dc, st, -1); } +#line 7642 "dvi_special.c" +yy536: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'o') goto yy426; - goto yy415; -yy424: + if (yych == 'h') goto yy540; + goto yy490; +yy537: ++cur; - yych = cur < lim ? *cur : -1; - if (yych == 'd') goto yy427; - goto yy415; -yy425: +#line 3558 "dvi_special.re2c.c" + { return 1; } +#line 7652 "dvi_special.c" +yy538: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ':') goto yy428; - if (yych == 'c') goto yy429; - goto yy415; -yy426: + if (yych == '=') goto yy541; + goto yy490; +yy539: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'r') goto yy430; - goto yy415; -yy427: + if (yych == 't') goto yy542; + goto yy490; +yy540: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 's') goto yy431; - goto yy415; -yy428: + if (yych == ' ') goto yy543; + goto yy490; +yy541: ++cur; yych = cur < lim ? *cur : -1; - if (yybm[0+yych] & 64) { - goto yy428; + if (yych == '-') { + yyt1 = cur; + goto yy544; } -#line 1270 "dvi_special.re2c.c" - { return dvi_exec_pdf(ctx, dc, st, cur, lim); } -#line 4598 "dvi_special.c" -yy429: + if (yych <= '/') goto yy490; + if (yych <= '9') { + yyt1 = cur; + goto yy545; + } + goto yy490; +yy542: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'o') goto yy432; - goto yy415; -yy430: + if (yych == 'a') goto yy546; + goto yy490; +yy543: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ' ') goto yy433; - goto yy415; -yy431: + if (yych == ' ') goto yy543; +#line 3586 "dvi_special.re2c.c" + { + return colorstack_push(ctx, dc, st, -1) && + parse_color(&st->gs.colors, cur, lim); + } +#line 7695 "dvi_special.c" +yy544: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'c') goto yy434; - goto yy415; -yy432: + if (yybm[0+yych] & 128) { + goto yy545; + } + goto yy490; +yy545: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'l') goto yy435; - goto yy415; -yy433: + if (yybm[0+yych] & 128) { + goto yy545; + } + if (yych <= 'i') { + if (yych <= 'b') { + if (yych == '.') goto yy547; + goto yy490; + } else { + if (yych <= 'c') goto yy548; + if (yych <= 'h') goto yy490; + goto yy549; + } + } else { + if (yych <= 'o') { + if (yych == 'm') goto yy548; + goto yy490; + } else { + if (yych <= 'p') goto yy550; + if (yych == 't') goto yy551; + goto yy490; + } + } +yy546: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ' ') goto yy433; - if (yych == 'p') goto yy436; - goto yy415; -yy434: + if (yych == 'c') goto yy552; + goto yy490; +yy547: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'a') goto yy437; - goto yy415; -yy435: + if (yych <= 'i') { + if (yych <= 'b') { + if (yych <= '/') goto yy490; + if (yych <= '9') goto yy547; + goto yy490; + } else { + if (yych <= 'c') goto yy548; + if (yych <= 'h') goto yy490; + goto yy549; + } + } else { + if (yych <= 'o') { + if (yych != 'm') goto yy490; + } else { + if (yych <= 'p') goto yy550; + if (yych == 't') goto yy551; + goto yy490; + } + } +yy548: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'o') goto yy438; - goto yy415; -yy436: + if (yych == 'm') goto yy553; + goto yy490; +yy549: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'o') goto yy439; - if (yych == 'u') goto yy440; - goto yy415; -yy437: + if (yych == 'n') goto yy553; + goto yy490; +yy550: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'p') goto yy441; - goto yy415; -yy438: + if (yych == 't') goto yy553; + goto yy490; +yy551: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'r') goto yy442; - goto yy415; -yy439: + if (yych == 'r') goto yy554; + goto yy490; +yy552: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'p') goto yy443; - goto yy415; -yy440: + if (yych == 'k') goto yy555; + goto yy490; +yy553: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 's') goto yy444; - goto yy415; -yy441: + if (yych == ',') goto yy556; + goto yy490; +yy554: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'e') goto yy445; - goto yy415; -yy442: + if (yych == 'u') goto yy557; + goto yy490; +yy555: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 's') goto yy446; - goto yy415; -yy443: - ++cur; -#line 1251 "dvi_special.re2c.c" - { return colorstack_pop(ctx, dc, st, -1); } -#line 4675 "dvi_special.c" -yy444: + if (yych == ' ') goto yy558; + if (yych == 'i') goto yy559; + goto yy490; +yy556: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'h') goto yy447; - goto yy415; -yy445: + if (yych == '-') { + yyt2 = cur; + goto yy560; + } + if (yych <= '/') goto yy490; + if (yych <= '9') { + yyt2 = cur; + goto yy561; + } + goto yy490; +yy557: ++cur; -#line 1226 "dvi_special.re2c.c" - { return 1; } -#line 4685 "dvi_special.c" -yy446: + yych = cur < lim ? *cur : -1; + if (yych == 'e') goto yy562; + goto yy490; +yy558: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 't') goto yy448; - goto yy415; -yy447: + if (yych == ' ') goto yy558; + if (yych <= '/') goto yy490; + if (yych <= '9') { + yyt1 = cur; + goto yy563; + } + goto yy490; +yy559: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ' ') goto yy449; - goto yy415; -yy448: + if (yych == 'n') goto yy564; + goto yy490; +yy560: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'a') goto yy450; - goto yy415; -yy449: + if (yych <= '/') goto yy490; + if (yych >= ':') goto yy490; +yy561: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ' ') goto yy449; -#line 1254 "dvi_special.re2c.c" - { - return colorstack_push(ctx, dc, st, -1) && - parse_color(&st->gs.colors, cur, lim); - } -#line 4710 "dvi_special.c" -yy450: + if (yych <= 'h') { + if (yych <= '/') { + if (yych == '.') goto yy565; + goto yy490; + } else { + if (yych <= '9') goto yy561; + if (yych == 'c') goto yy566; + goto yy490; + } + } else { + if (yych <= 'o') { + if (yych <= 'i') goto yy567; + if (yych == 'm') goto yy566; + goto yy490; + } else { + if (yych <= 'p') goto yy568; + if (yych == 't') goto yy569; + goto yy490; + } + } +yy562: ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'c') goto yy415; + switch (yych) { + case 'c': + case 'm': goto yy548; + case 'i': goto yy549; + case 'p': goto yy550; + default: goto yy490; + } +yy563: ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'k') goto yy415; + if (yych == ' ') goto yy570; + if (yych <= '/') goto yy490; + if (yych <= '9') goto yy563; + goto yy490; +yy564: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ' ') goto yy451; - if (yych == 'i') goto yy452; - goto yy415; -yy451: + if (yych == 'i') goto yy571; + goto yy490; +yy565: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ' ') goto yy451; - if (yych <= '/') goto yy415; - if (yych <= '9') { - yyt1 = cur; - goto yy453; + if (yych <= 'i') { + if (yych <= 'b') { + if (yych <= '/') goto yy490; + if (yych <= '9') goto yy565; + goto yy490; + } else { + if (yych <= 'c') goto yy566; + if (yych <= 'h') goto yy490; + goto yy567; + } + } else { + if (yych <= 'o') { + if (yych != 'm') goto yy490; + } else { + if (yych <= 'p') goto yy568; + if (yych == 't') goto yy569; + goto yy490; + } } - goto yy415; -yy452: +yy566: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'n') goto yy454; - goto yy415; -yy453: + if (yych == 'm') goto yy572; + goto yy490; +yy567: ++cur; yych = cur < lim ? *cur : -1; - if (yybm[0+yych] & 128) { - goto yy453; - } - if (yych == ' ') goto yy455; - goto yy415; -yy454: + if (yych == 'n') goto yy572; + goto yy490; +yy568: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'i') goto yy456; - goto yy415; -yy455: + if (yych == 't') goto yy572; + goto yy490; +yy569: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'r') goto yy573; + goto yy490; +yy570: ++cur; yych = cur < lim ? *cur : -1; if (yych <= 'b') { - if (yych == ' ') goto yy455; - goto yy415; + if (yych == ' ') goto yy570; + goto yy490; } else { - if (yych <= 'c') goto yy457; - if (yych == 'p') goto yy458; - goto yy415; + if (yych <= 'c') goto yy574; + if (yych == 'p') goto yy575; + goto yy490; } -yy456: +yy571: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 't') goto yy459; - goto yy415; -yy457: + if (yych == 't') goto yy576; + goto yy490; +yy572: + ++cur; + i = yyt1; + j = yyt2; +#line 3605 "dvi_special.re2c.c" + { + dc->page_width = pdim(i, lim); + dc->page_height = pdim(j, lim); + return 1; + } +#line 7947 "dvi_special.c" +yy573: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'u') goto yy460; - goto yy415; -yy458: + if (yych == 'u') goto yy577; + goto yy490; +yy574: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'o') goto yy461; - if (yych == 'u') goto yy462; - goto yy415; -yy459: + if (yych == 'u') goto yy578; + goto yy490; +yy575: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ' ') goto yy463; - goto yy415; -yy460: + if (yych == 'o') goto yy579; + if (yych == 'u') goto yy580; + goto yy490; +yy576: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'r') goto yy464; - goto yy415; -yy461: + if (yych == ' ') goto yy581; + goto yy490; +yy577: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'p') goto yy465; - goto yy415; -yy462: + if (yych == 'e') goto yy582; + goto yy490; +yy578: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 's') goto yy466; - goto yy415; -yy463: + if (yych == 'r') goto yy583; + goto yy490; +yy579: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'p') goto yy584; + goto yy490; +yy580: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 's') goto yy585; + goto yy490; +yy581: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ' ') goto yy463; - if (yych <= '/') goto yy415; + if (yych == ' ') goto yy581; + if (yych <= '/') goto yy490; if (yych <= '9') { yyt1 = cur; - goto yy467; + goto yy586; } - goto yy415; -yy464: + goto yy490; +yy582: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'r') goto yy468; - goto yy415; -yy465: + switch (yych) { + case 'c': + case 'm': goto yy566; + case 'i': goto yy567; + case 'p': goto yy568; + default: goto yy490; + } +yy583: + ++cur; + yych = cur < lim ? *cur : -1; + if (yych == 'r') goto yy587; + goto yy490; +yy584: ++cur; i = yyt1; -#line 1234 "dvi_special.re2c.c" +#line 3566 "dvi_special.re2c.c" { return colorstack_pop(ctx, dc, st, pint(i, lim)); } -#line 4818 "dvi_special.c" -yy466: +#line 8019 "dvi_special.c" +yy585: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'h') goto yy469; - goto yy415; -yy467: + if (yych == 'h') goto yy588; + goto yy490; +yy586: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ' ') goto yy470; - if (yych <= '/') goto yy415; - if (yych <= '9') goto yy467; - goto yy415; -yy468: + if (yych == ' ') goto yy589; + if (yych <= '/') goto yy490; + if (yych <= '9') goto yy586; + goto yy490; +yy587: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'e') goto yy471; - goto yy415; -yy469: + if (yych == 'e') goto yy590; + goto yy490; +yy588: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ' ') goto yy472; - goto yy415; -yy470: + if (yych == ' ') goto yy591; + goto yy490; +yy589: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ' ') goto yy470; - if (yych == 'p') goto yy473; - goto yy415; -yy471: + if (yych == ' ') goto yy589; + if (yych == 'p') goto yy592; + goto yy490; +yy590: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'n') goto yy474; - goto yy415; -yy472: + if (yych == 'n') goto yy593; + goto yy490; +yy591: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ' ') goto yy472; - if (yych == '(') goto yy475; - goto yy415; -yy473: + if (yych == ' ') goto yy591; + if (yych == '(') goto yy594; + goto yy490; +yy592: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'a') goto yy476; - goto yy415; -yy474: + if (yych == 'a') goto yy595; + goto yy490; +yy593: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 't') goto yy477; - goto yy415; -yy475: + if (yych == 't') goto yy596; + goto yy490; +yy594: ++cur; i = yyt1; -#line 1238 "dvi_special.re2c.c" +#line 3570 "dvi_special.re2c.c" { return colorstack_push(ctx, dc, st, pint(i, lim)) && parse_pdfcolor(&st->gs.colors, cur, lim); } -#line 4876 "dvi_special.c" -yy476: +#line 8077 "dvi_special.c" +yy595: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'g') goto yy478; - goto yy415; -yy477: + if (yych == 'g') goto yy597; + goto yy490; +yy596: ++cur; i = yyt1; -#line 1230 "dvi_special.re2c.c" +#line 3562 "dvi_special.re2c.c" { return pdfcolorstack_current(ctx, dc, st, pint(i, lim)); } -#line 4887 "dvi_special.c" -yy478: +#line 8088 "dvi_special.c" +yy597: ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'e') goto yy415; + if (yych != 'e') goto yy490; ++cur; yych = cur < lim ? *cur : -1; - if (yych != ' ') goto yy415; -yy479: + if (yych != ' ') goto yy490; +yy598: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ' ') goto yy479; - if (yych != 'd') goto yy415; + if (yych == ' ') goto yy598; + if (yych != 'd') goto yy490; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'i') goto yy415; + if (yych != 'i') goto yy490; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'r') goto yy415; + if (yych != 'r') goto yy490; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'e') goto yy415; + if (yych != 'e') goto yy490; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'c') goto yy415; + if (yych != 'c') goto yy490; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 't') goto yy415; + if (yych != 't') goto yy490; ++cur; yych = cur < lim ? *cur : -1; - if (yych != ' ') goto yy415; -yy480: + if (yych != ' ') goto yy490; +yy599: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ' ') goto yy480; - if (yych != '(') goto yy415; + if (yych == ' ') goto yy599; + if (yych != '(') goto yy490; ++cur; i = yyt1; -#line 1245 "dvi_special.re2c.c" +#line 3577 "dvi_special.re2c.c" { return colorstack_init(ctx, dc, st, pint(i, lim)) && parse_pdfcolor(&st->gs.colors, cur, lim); } -#line 4930 "dvi_special.c" +#line 8131 "dvi_special.c" } -#line 1275 "dvi_special.re2c.c" +#line 3650 "dvi_special.re2c.c" } } @@ -4937,181 +8138,181 @@ bool dvi_init_special(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t { cursor_t i, f0, f1, mar; - -#line 4942 "dvi_special.c" + +#line 8143 "dvi_special.c" { int yych; static const unsigned char yybm[] = { - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 64, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 64, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, }; mar = cur; yych = cur < lim ? *cur : -1; - if (yych == 'p') goto yy483; -yy482: -#line 1293 "dvi_special.re2c.c" + if (yych == 'p') goto yy602; +yy601: +#line 3668 "dvi_special.re2c.c" { return 0; } -#line 4985 "dvi_special.c" -yy483: +#line 8186 "dvi_special.c" +yy602: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'd') goto yy485; -yy484: + if (yych == 'd') goto yy604; +yy603: cur = mar; - goto yy482; -yy485: + goto yy601; +yy604: ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'f') goto yy484; + if (yych != 'f') goto yy603; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'c') goto yy484; + if (yych != 'c') goto yy603; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'o') goto yy484; + if (yych != 'o') goto yy603; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'l') goto yy484; + if (yych != 'l') goto yy603; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'o') goto yy484; + if (yych != 'o') goto yy603; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'r') goto yy484; + if (yych != 'r') goto yy603; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 's') goto yy484; + if (yych != 's') goto yy603; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 't') goto yy484; + if (yych != 't') goto yy603; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'a') goto yy484; + if (yych != 'a') goto yy603; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'c') goto yy484; + if (yych != 'c') goto yy603; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'k') goto yy484; + if (yych != 'k') goto yy603; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'i') goto yy484; + if (yych != 'i') goto yy603; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'n') goto yy484; + if (yych != 'n') goto yy603; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'i') goto yy484; + if (yych != 'i') goto yy603; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 't') goto yy484; + if (yych != 't') goto yy603; ++cur; yych = cur < lim ? *cur : -1; if (yybm[0+yych] & 64) { - goto yy486; + goto yy605; } - goto yy484; -yy486: + goto yy603; +yy605: ++cur; yych = cur < lim ? *cur : -1; if (yybm[0+yych] & 64) { - goto yy486; + goto yy605; } - if (yych <= '/') goto yy484; - if (yych >= ':') goto yy484; + if (yych <= '/') goto yy603; + if (yych >= ':') goto yy603; yyt1 = cur; -yy487: +yy606: ++cur; yych = cur < lim ? *cur : -1; if (yybm[0+yych] & 128) { - goto yy487; + goto yy606; } - if (yych != ' ') goto yy484; -yy488: + if (yych != ' ') goto yy603; +yy607: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ' ') goto yy488; - if (yych != 'p') goto yy484; + if (yych == ' ') goto yy607; + if (yych != 'p') goto yy603; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'a') goto yy484; + if (yych != 'a') goto yy603; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'g') goto yy484; + if (yych != 'g') goto yy603; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'e') goto yy484; + if (yych != 'e') goto yy603; ++cur; yych = cur < lim ? *cur : -1; - if (yych != ' ') goto yy484; -yy489: + if (yych != ' ') goto yy603; +yy608: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ' ') goto yy489; - if (yych != 'd') goto yy484; + if (yych == ' ') goto yy608; + if (yych != 'd') goto yy603; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'i') goto yy484; + if (yych != 'i') goto yy603; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'r') goto yy484; + if (yych != 'r') goto yy603; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'e') goto yy484; + if (yych != 'e') goto yy603; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'c') goto yy484; + if (yych != 'c') goto yy603; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 't') goto yy484; + if (yych != 't') goto yy603; ++cur; yych = cur < lim ? *cur : -1; - if (yych != ' ') goto yy484; -yy490: + if (yych != ' ') goto yy603; +yy609: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ' ') goto yy490; - if (yych != '(') goto yy484; + if (yych == ' ') goto yy609; + if (yych != '(') goto yy603; ++cur; i = yyt1; -#line 1287 "dvi_special.re2c.c" +#line 3662 "dvi_special.re2c.c" { return colorstack_init(ctx, dc, st, pint(i, lim)) && parse_pdfcolor(&st->gs.colors, cur, lim); } -#line 5113 "dvi_special.c" +#line 8314 "dvi_special.c" } -#line 1295 "dvi_special.re2c.c" +#line 3670 "dvi_special.re2c.c" } @@ -5121,487 +8322,487 @@ void dvi_prescan_special(cursor_t cur, cursor_t lim, float *width, float *height // fprintf(stderr, "prescan: %.*s\n", (int)(lim - cur), cur); - -#line 5126 "dvi_special.c" + +#line 8327 "dvi_special.c" { int yych; static const unsigned char yybm[] = { - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 64, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 64, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, }; mar = cur; yych = cur < lim ? *cur : -1; - if (yych == 'l') goto yy493; - if (yych == 'p') goto yy495; -yy492: -#line 1324 "dvi_special.re2c.c" + if (yych == 'l') goto yy612; + if (yych == 'p') goto yy614; +yy611: +#line 3699 "dvi_special.re2c.c" { return; } -#line 5170 "dvi_special.c" -yy493: +#line 8371 "dvi_special.c" +yy612: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'a') goto yy496; -yy494: + if (yych == 'a') goto yy615; +yy613: cur = mar; - goto yy492; -yy495: + goto yy611; +yy614: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'd') goto yy497; - goto yy494; -yy496: + if (yych == 'd') goto yy616; + goto yy613; +yy615: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'n') goto yy498; - goto yy494; -yy497: + if (yych == 'n') goto yy617; + goto yy613; +yy616: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'f') goto yy499; - goto yy494; -yy498: + if (yych == 'f') goto yy618; + goto yy613; +yy617: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'd') goto yy500; - goto yy494; -yy499: + if (yych == 'd') goto yy619; + goto yy613; +yy618: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ':') goto yy501; - goto yy494; -yy500: + if (yych == ':') goto yy620; + goto yy613; +yy619: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 's') goto yy502; - goto yy494; -yy501: + if (yych == 's') goto yy621; + goto yy613; +yy620: ++cur; yych = cur < lim ? *cur : -1; if (yybm[0+yych] & 64) { - goto yy501; + goto yy620; } - if (yych == 'p') goto yy503; - goto yy494; -yy502: + if (yych == 'p') goto yy622; + goto yy613; +yy621: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'c') goto yy504; - goto yy494; -yy503: + if (yych == 'c') goto yy623; + goto yy613; +yy622: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'a') goto yy505; - goto yy494; -yy504: + if (yych == 'a') goto yy624; + goto yy613; +yy623: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'a') goto yy506; - goto yy494; -yy505: + if (yych == 'a') goto yy625; + goto yy613; +yy624: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'g') goto yy507; - goto yy494; -yy506: + if (yych == 'g') goto yy626; + goto yy613; +yy625: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'p') goto yy508; - goto yy494; -yy507: + if (yych == 'p') goto yy627; + goto yy613; +yy626: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'e') goto yy509; - goto yy494; -yy508: + if (yych == 'e') goto yy628; + goto yy613; +yy627: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'e') goto yy510; - goto yy494; -yy509: + if (yych == 'e') goto yy629; + goto yy613; +yy628: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 's') goto yy511; - goto yy494; -yy510: + if (yych == 's') goto yy630; + goto yy613; +yy629: ++cur; -#line 1307 "dvi_special.re2c.c" +#line 3682 "dvi_special.re2c.c" { *landscape = 1; return; } -#line 5263 "dvi_special.c" -yy511: +#line 8464 "dvi_special.c" +yy630: ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'i') goto yy494; + if (yych != 'i') goto yy613; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'z') goto yy494; + if (yych != 'z') goto yy613; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'e') goto yy494; + if (yych != 'e') goto yy613; ++cur; yych = cur < lim ? *cur : -1; - if (yych != ' ') goto yy494; -yy512: + if (yych != ' ') goto yy613; +yy631: ++cur; yych = cur < lim ? *cur : -1; if (yych <= 'c') { - if (yych == ' ') goto yy512; - goto yy494; + if (yych == ' ') goto yy631; + goto yy613; } else { - if (yych <= 'd') goto yy513; - if (yych == 'w') goto yy514; - goto yy494; + if (yych <= 'd') goto yy632; + if (yych == 'w') goto yy633; + goto yy613; } -yy513: +yy632: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'e') goto yy515; - goto yy494; -yy514: + if (yych == 'e') goto yy634; + goto yy613; +yy633: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'i') goto yy516; - goto yy494; -yy515: + if (yych == 'i') goto yy635; + goto yy613; +yy634: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'f') goto yy517; - goto yy494; -yy516: + if (yych == 'f') goto yy636; + goto yy613; +yy635: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'd') goto yy518; - goto yy494; -yy517: + if (yych == 'd') goto yy637; + goto yy613; +yy636: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'a') goto yy519; - goto yy494; -yy518: + if (yych == 'a') goto yy638; + goto yy613; +yy637: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 't') goto yy520; - goto yy494; -yy519: + if (yych == 't') goto yy639; + goto yy613; +yy638: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'u') goto yy521; - goto yy494; -yy520: + if (yych == 'u') goto yy640; + goto yy613; +yy639: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'h') goto yy522; - goto yy494; -yy521: + if (yych == 'h') goto yy641; + goto yy613; +yy640: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'l') goto yy523; - goto yy494; -yy522: + if (yych == 'l') goto yy642; + goto yy613; +yy641: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ' ') goto yy524; - goto yy494; -yy523: + if (yych == ' ') goto yy643; + goto yy613; +yy642: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 't') goto yy525; - goto yy494; -yy524: + if (yych == 't') goto yy644; + goto yy613; +yy643: ++cur; yych = cur < lim ? *cur : -1; if (yych <= ',') { - if (yych == ' ') goto yy524; - goto yy494; + if (yych == ' ') goto yy643; + goto yy613; } else { if (yych <= '-') { yyt1 = cur; - goto yy526; + goto yy645; } - if (yych <= '/') goto yy494; + if (yych <= '/') goto yy613; if (yych <= '9') { yyt1 = cur; - goto yy527; + goto yy646; } - goto yy494; + goto yy613; } -yy525: +yy644: ++cur; -#line 1321 "dvi_special.re2c.c" +#line 3696 "dvi_special.re2c.c" { *width = 612; *height = 792; return; } -#line 5365 "dvi_special.c" -yy526: +#line 8566 "dvi_special.c" +yy645: ++cur; yych = cur < lim ? *cur : -1; if (yybm[0+yych] & 128) { - goto yy527; + goto yy646; } - goto yy494; -yy527: + goto yy613; +yy646: ++cur; yych = cur < lim ? *cur : -1; if (yybm[0+yych] & 128) { - goto yy527; + goto yy646; } if (yych <= 'i') { if (yych <= 'b') { - if (yych != '.') goto yy494; + if (yych != '.') goto yy613; } else { - if (yych <= 'c') goto yy529; - if (yych <= 'h') goto yy494; - goto yy530; + if (yych <= 'c') goto yy648; + if (yych <= 'h') goto yy613; + goto yy649; } } else { if (yych <= 'o') { - if (yych == 'm') goto yy529; - goto yy494; + if (yych == 'm') goto yy648; + goto yy613; } else { - if (yych <= 'p') goto yy531; - if (yych == 't') goto yy532; - goto yy494; + if (yych <= 'p') goto yy650; + if (yych == 't') goto yy651; + goto yy613; } } -yy528: +yy647: ++cur; yych = cur < lim ? *cur : -1; if (yych <= 'i') { if (yych <= 'b') { - if (yych <= '/') goto yy494; - if (yych <= '9') goto yy528; - goto yy494; + if (yych <= '/') goto yy613; + if (yych <= '9') goto yy647; + goto yy613; } else { - if (yych <= 'c') goto yy529; - if (yych <= 'h') goto yy494; - goto yy530; + if (yych <= 'c') goto yy648; + if (yych <= 'h') goto yy613; + goto yy649; } } else { if (yych <= 'o') { - if (yych != 'm') goto yy494; + if (yych != 'm') goto yy613; } else { - if (yych <= 'p') goto yy531; - if (yych == 't') goto yy532; - goto yy494; + if (yych <= 'p') goto yy650; + if (yych == 't') goto yy651; + goto yy613; } } -yy529: +yy648: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'm') goto yy533; - goto yy494; -yy530: + if (yych == 'm') goto yy652; + goto yy613; +yy649: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'n') goto yy533; - goto yy494; -yy531: + if (yych == 'n') goto yy652; + goto yy613; +yy650: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 't') goto yy533; - goto yy494; -yy532: + if (yych == 't') goto yy652; + goto yy613; +yy651: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'r') goto yy534; - goto yy494; -yy533: + if (yych == 'r') goto yy653; + goto yy613; +yy652: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ' ') goto yy535; - goto yy494; -yy534: + if (yych == ' ') goto yy654; + goto yy613; +yy653: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'u') goto yy536; - goto yy494; -yy535: + if (yych == 'u') goto yy655; + goto yy613; +yy654: ++cur; yych = cur < lim ? *cur : -1; - if (yych == ' ') goto yy535; - if (yych == 'h') goto yy537; - goto yy494; -yy536: + if (yych == ' ') goto yy654; + if (yych == 'h') goto yy656; + goto yy613; +yy655: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'e') goto yy538; - goto yy494; -yy537: + if (yych == 'e') goto yy657; + goto yy613; +yy656: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'e') goto yy539; - goto yy494; -yy538: + if (yych == 'e') goto yy658; + goto yy613; +yy657: ++cur; yych = cur < lim ? *cur : -1; switch (yych) { case 'c': - case 'm': goto yy529; - case 'i': goto yy530; - case 'p': goto yy531; - default: goto yy494; + case 'm': goto yy648; + case 'i': goto yy649; + case 'p': goto yy650; + default: goto yy613; } -yy539: +yy658: ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'i') goto yy494; + if (yych != 'i') goto yy613; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'g') goto yy494; + if (yych != 'g') goto yy613; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'h') goto yy494; + if (yych != 'h') goto yy613; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 't') goto yy494; + if (yych != 't') goto yy613; ++cur; yych = cur < lim ? *cur : -1; - if (yych != ' ') goto yy494; -yy540: + if (yych != ' ') goto yy613; +yy659: ++cur; yych = cur < lim ? *cur : -1; if (yych <= ',') { - if (yych == ' ') goto yy540; - goto yy494; + if (yych == ' ') goto yy659; + goto yy613; } else { if (yych <= '-') { yyt2 = cur; - goto yy541; + goto yy660; } - if (yych <= '/') goto yy494; + if (yych <= '/') goto yy613; if (yych <= '9') { yyt2 = cur; - goto yy542; + goto yy661; } - goto yy494; + goto yy613; } -yy541: +yy660: ++cur; yych = cur < lim ? *cur : -1; - if (yych <= '/') goto yy494; - if (yych >= ':') goto yy494; -yy542: + if (yych <= '/') goto yy613; + if (yych >= ':') goto yy613; +yy661: ++cur; yych = cur < lim ? *cur : -1; if (yych <= 'h') { if (yych <= '/') { - if (yych != '.') goto yy494; + if (yych != '.') goto yy613; } else { - if (yych <= '9') goto yy542; - if (yych == 'c') goto yy544; - goto yy494; + if (yych <= '9') goto yy661; + if (yych == 'c') goto yy663; + goto yy613; } } else { if (yych <= 'o') { - if (yych <= 'i') goto yy545; - if (yych == 'm') goto yy544; - goto yy494; + if (yych <= 'i') goto yy664; + if (yych == 'm') goto yy663; + goto yy613; } else { - if (yych <= 'p') goto yy546; - if (yych == 't') goto yy547; - goto yy494; + if (yych <= 'p') goto yy665; + if (yych == 't') goto yy666; + goto yy613; } } -yy543: +yy662: ++cur; yych = cur < lim ? *cur : -1; if (yych <= 'i') { if (yych <= 'b') { - if (yych <= '/') goto yy494; - if (yych <= '9') goto yy543; - goto yy494; + if (yych <= '/') goto yy613; + if (yych <= '9') goto yy662; + goto yy613; } else { - if (yych <= 'c') goto yy544; - if (yych <= 'h') goto yy494; - goto yy545; + if (yych <= 'c') goto yy663; + if (yych <= 'h') goto yy613; + goto yy664; } } else { if (yych <= 'o') { - if (yych != 'm') goto yy494; + if (yych != 'm') goto yy613; } else { - if (yych <= 'p') goto yy546; - if (yych == 't') goto yy547; - goto yy494; + if (yych <= 'p') goto yy665; + if (yych == 't') goto yy666; + goto yy613; } } -yy544: +yy663: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'm') goto yy548; - goto yy494; -yy545: + if (yych == 'm') goto yy667; + goto yy613; +yy664: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'n') goto yy548; - goto yy494; -yy546: + if (yych == 'n') goto yy667; + goto yy613; +yy665: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 't') goto yy548; - goto yy494; -yy547: + if (yych == 't') goto yy667; + goto yy613; +yy666: ++cur; yych = cur < lim ? *cur : -1; - if (yych == 'r') goto yy549; - goto yy494; -yy548: + if (yych == 'r') goto yy668; + goto yy613; +yy667: ++cur; f0 = yyt1; f1 = yyt2; -#line 1314 "dvi_special.re2c.c" +#line 3689 "dvi_special.re2c.c" { *width = pdim(f0, lim); *height = pdim(f1, lim); return; } -#line 5588 "dvi_special.c" -yy549: +#line 8789 "dvi_special.c" +yy668: ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'u') goto yy494; + if (yych != 'u') goto yy613; ++cur; yych = cur < lim ? *cur : -1; - if (yych != 'e') goto yy494; + if (yych != 'e') goto yy613; ++cur; yych = cur < lim ? *cur : -1; switch (yych) { case 'c': - case 'm': goto yy544; - case 'i': goto yy545; - case 'p': goto yy546; - default: goto yy494; + case 'm': goto yy663; + case 'i': goto yy664; + case 'p': goto yy665; + default: goto yy613; } } -#line 1326 "dvi_special.re2c.c" +#line 3701 "dvi_special.re2c.c" } diff --git a/src/dvi/dvi_special.re2c.c b/src/dvi/dvi_special.re2c.c index f4878b6e..4c67ef83 100644 --- a/src/dvi/dvi_special.re2c.c +++ b/src/dvi/dvi_special.re2c.c @@ -34,6 +34,7 @@ #define device_cs fz_device_rgb #define color_params fz_default_color_params + typedef const char *cursor_t; /*!re2c @@ -69,9 +70,10 @@ dim = float unit; static bool unhandled(const char *kind, cursor_t cur, cursor_t lim, int ignored) { - if (0 && !ignored) - fprintf(stderr, "unhandled %s: \"%.*s\"\n", kind, (int)(lim - cur), cur); - return 0; + (void)kind; (void)cur; (void)lim; (void)ignored; + // Don't print warnings for unrecognized specials to keep output clean. + // Return 1 to prevent aborting page rendering. + return 1; } static int pnat(cursor_t ptr, cursor_t lim) @@ -500,7 +502,7 @@ embed_image(fz_context *ctx, dvi_context *dc, dvi_state *st, struct xform_spec * else if (h != h) h = w / ar; ctm = fz_pre_scale(fz_pre_translate(ctm, 0, h), w, -h); - fz_fill_image(ctx, dc->dev, img, ctm, 1.0, color_params); + fz_fill_image(ctx, dc->dev, img, ctm, st->gs.fill_alpha, color_params); } fz_always(ctx) { @@ -731,6 +733,148 @@ static void get_stroke_state(fz_context *ctx, dvi_state *st, fz_stroke_state *st stst->dash_phase = st->gs.dash_phase; } +// Resolve a font by name for TikZ text rendering. +// Uses a simple cache in dvi_context to avoid reloading fonts. +static fz_font *resolve_special_font(fz_context *ctx, dvi_context *dc, dvi_state *st, const char *name) +{ + if (!name || !*name) + return NULL; + + // Skip leading '/' + const char *fname = (*name == '/') ? name + 1 : name; + + // Check cache + for (int i = 0; i < dc->font_cache_count; i++) + { + if (strcmp(dc->font_cache[i].name, fname) == 0) + return dc->font_cache[i].font; + } + + // Try MuPDF built-in font + fz_font *font = NULL; + fz_try(ctx) + { + font = fz_new_font_from_file(ctx, NULL, fname, 0, 0); + } + fz_catch(ctx) + { + font = NULL; + } + + // Fallback: try current DVI font + if (!font) + { + dvi_fontdef *def = dvi_fonttable_get(ctx, st->fonts, st->f); + if (def && def->kind == TEX_FONT && def->tex_font.font && def->tex_font.font->fz) + font = def->tex_font.font->fz; + } + + // Cache it + if (font && dc->font_cache_count < DVI_FONT_CACHE_SIZE) + { + size_t len = strlen(fname); + if (len > 63) len = 63; + memcpy(dc->font_cache[dc->font_cache_count].name, fname, len); + dc->font_cache[dc->font_cache_count].name[len] = 0; + dc->font_cache[dc->font_cache_count].font = font; + dc->font_cache_count++; + } + + return font; +} + +// Decode a single UTF-8 code point from str at offset *pi. +// Returns the Unicode code point and advances *pi past the consumed bytes. +// Returns 0 and advances by 1 byte on invalid sequence. +static int decode_utf8(const char *str, size_t len, size_t *pi) +{ + size_t i = *pi; + unsigned char c = (unsigned char)str[i]; + int cp; + int extra; + + if (c < 0x80) { + *pi = i + 1; + return c; + } + if (c < 0xC2) goto invalid; + if (c < 0xE0) { cp = c & 0x1F; extra = 1; } + else if (c < 0xF0) { cp = c & 0x0F; extra = 2; } + else if (c < 0xF8) { cp = c & 0x07; extra = 3; } + else goto invalid; + + *pi = i + 1 + extra; + if (i + extra >= len) return 0; + for (int j = 0; j < extra; j++) { + unsigned char nb = (unsigned char)str[i + 1 + j]; + if ((nb & 0xC0) != 0x80) return 0; + cp = (cp << 6) | (nb & 0x3F); + } + return cp; + +invalid: + *pi = i + 1; + return 0; +} + +// Show a text string for Tj operator, advancing text matrix. +static void show_special_text(fz_context *ctx, dvi_context *dc, dvi_state *st, + const char *str, size_t len, fz_font *font) +{ + if (!font || !str || !len || !dc->dev) + return; + + if (!dc->text) + dc->text = fz_new_text(ctx); + fz_text *text = dc->text; + fz_matrix ctm = dvi_get_ctm(dc, st); + float fs = st->gs.text.font_size; + float hs = st->gs.text.scale; + + size_t i = 0; + while (i < len) + { + int cp = decode_utf8(str, len, &i); + if (cp == 0) + continue; + int glyph = fz_encode_character(ctx, font, cp); + if (glyph == 0) + { + // For missing glyphs, advance Tm by a default space to avoid overlap + float tx = fs * 0.25f * hs; + st->gs.text.Tm = fz_pre_translate(st->gs.text.Tm, tx, 0); + continue; + } + float adv = fz_advance_glyph(ctx, font, glyph, 0); + // TRM = translate(0,rise) × CTM × Tm × Tfs + fz_matrix trm = fz_pre_scale(fz_identity, fs * hs, fs); // Tfs + trm = fz_concat(trm, st->gs.text.Tm); // Tfs × Tm + trm = fz_concat(trm, ctm); // (Tfs × Tm) × CTM + trm = fz_pre_translate(trm, 0, st->gs.text.rise); // apply text rise + fz_show_glyph(ctx, text, font, trm, glyph, cp, 0, 0, FZ_BIDI_LTR, FZ_LANG_UNSET); + // Advance text matrix; word_space only applies to space chars (U+0020) + float tx = (adv * fs + st->gs.text.char_space) * hs; + if (cp == 32) tx += st->gs.text.word_space; + st->gs.text.Tm = fz_pre_translate(st->gs.text.Tm, tx, 0); + } +} + + +// Forward declarations and globals for pattern support (defined later in this file) +static int pdf_pat_lookup(const char *pat_name); +static void render_pdf_pattern_fill(fz_context *ctx, dvi_context *dc, dvi_state *st, int even_odd); + +// Pattern state: must be declared before pdf_code which uses them. +// The full pattern table (pdf_pats[]) is defined later; these are only the +// run-time state variables needed by the fill operators in pdf_code. +static int in_pattern_tile = 0; // recursion guard + +static struct { + int active; // 1 if pattern fill mode is on + int pat_index; // index into pdf_pats[] + float color[3]; // pattern color (for uncolored patterns) +} active_pattern = {0, -1, {0,0,0}}; + static bool pdf_code(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, cursor_t lim) { @@ -738,7 +882,6 @@ pdf_code(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, cursor_t fz_var(cur); fz_var(stack); - // fprintf(stderr, "pdf code: %.*s\n", (int)(lim - cur), cur); fz_try(ctx) { enum PDF_OP op; @@ -791,6 +934,7 @@ pdf_code(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, cursor_t float c[1]; vstack_get_floats(ctx, stack, c, 1); color_set_gray(st->gs.colors.fill, c[0]); + active_pattern.active = 0; break; } case PDF_OP_RG: @@ -805,6 +949,7 @@ pdf_code(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, cursor_t float c[3]; vstack_get_floats(ctx, stack, c, 3); color_set_rgb(st->gs.colors.fill, c[0], c[1], c[2]); + active_pattern.active = 0; break; } case PDF_OP_K: @@ -819,6 +964,7 @@ pdf_code(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, cursor_t float c[4]; vstack_get_floats(ctx, stack, c, 4); color_set_cmyk(st->gs.colors.fill, c[0], c[1], c[2], c[3]); + active_pattern.active = 0; break; } @@ -877,86 +1023,118 @@ pdf_code(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, cursor_t } case PDF_OP_b: - if (dc->dev) - { + { + if (dc->path) fz_closepath(ctx, get_path(ctx, dc)); + if (active_pattern.active && !in_pattern_tile) { + render_pdf_pattern_fill(ctx, dc, st, 0); + } else if (dc->dev) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_path *path = get_path(ctx, dc); + fz_fill_path(ctx, dc->dev, path, 0, ctm, device_cs(ctx), + st->gs.colors.fill, st->gs.fill_alpha, color_params); + } + if (dc->dev && dc->path) { fz_matrix ctm = dvi_get_ctm(dc, st); fz_stroke_state stst; get_stroke_state(ctx, st, &stst); fz_path *path = get_path(ctx, dc); - fz_closepath(ctx, path); - fz_fill_path(ctx, dc->dev, path, 0, ctm, device_cs(ctx), - st->gs.colors.fill, 1.0, color_params); fz_stroke_path(ctx, dc->dev, path, &stst, ctm, device_cs(ctx), - st->gs.colors.line, 1.0, color_params); + st->gs.colors.line, st->gs.stroke_alpha, color_params); } drop_path(ctx, dc); break; + } case PDF_OP_b_star: - if (dc->dev) - { + { + if (dc->path) fz_closepath(ctx, get_path(ctx, dc)); + if (active_pattern.active && !in_pattern_tile) { + render_pdf_pattern_fill(ctx, dc, st, 1); + } else if (dc->dev) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_path *path = get_path(ctx, dc); + fz_fill_path(ctx, dc->dev, path, 1, ctm, device_cs(ctx), + st->gs.colors.fill, st->gs.fill_alpha, color_params); + } + if (dc->dev && dc->path) { fz_matrix ctm = dvi_get_ctm(dc, st); fz_stroke_state stst; get_stroke_state(ctx, st, &stst); fz_path *path = get_path(ctx, dc); - fz_closepath(ctx, path); - fz_fill_path(ctx, dc->dev, path, 1, ctm, device_cs(ctx), - st->gs.colors.fill, 1.0, color_params); fz_stroke_path(ctx, dc->dev, path, &stst, ctm, device_cs(ctx), - st->gs.colors.line, 1.0, color_params); + st->gs.colors.line, st->gs.stroke_alpha, color_params); } drop_path(ctx, dc); break; + } case PDF_OP_B: - if (dc->dev) - { + { + if (active_pattern.active && !in_pattern_tile) { + render_pdf_pattern_fill(ctx, dc, st, 0); + } else if (dc->dev) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_path *path = get_path(ctx, dc); + fz_fill_path(ctx, dc->dev, path, 0, ctm, device_cs(ctx), + st->gs.colors.fill, st->gs.fill_alpha, color_params); + } + if (dc->dev && dc->path) { fz_matrix ctm = dvi_get_ctm(dc, st); fz_stroke_state stst; get_stroke_state(ctx, st, &stst); fz_path *path = get_path(ctx, dc); - fz_fill_path(ctx, dc->dev, path, 0, ctm, device_cs(ctx), - st->gs.colors.fill, 1.0, color_params); fz_stroke_path(ctx, dc->dev, path, &stst, ctm, device_cs(ctx), - st->gs.colors.line, 1.0, color_params); + st->gs.colors.line, st->gs.stroke_alpha, color_params); } drop_path(ctx, dc); break; + } case PDF_OP_B_star: - if (dc->dev) - { + { + if (active_pattern.active && !in_pattern_tile) { + render_pdf_pattern_fill(ctx, dc, st, 1); + } else if (dc->dev) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_path *path = get_path(ctx, dc); + fz_fill_path(ctx, dc->dev, path, 1, ctm, device_cs(ctx), + st->gs.colors.fill, st->gs.fill_alpha, color_params); + } + if (dc->dev && dc->path) { fz_matrix ctm = dvi_get_ctm(dc, st); fz_stroke_state stst; get_stroke_state(ctx, st, &stst); fz_path *path = get_path(ctx, dc); - fz_fill_path(ctx, dc->dev, path, 1, ctm, device_cs(ctx), - st->gs.colors.fill, 1.0, color_params); fz_stroke_path(ctx, dc->dev, path, &stst, ctm, device_cs(ctx), - st->gs.colors.fill, 1.0, color_params); + st->gs.colors.line, st->gs.stroke_alpha, color_params); } drop_path(ctx, dc); break; + } case PDF_OP_f: case PDF_OP_F: - if (dc->dev) - { + if (active_pattern.active && !in_pattern_tile) { + render_pdf_pattern_fill(ctx, dc, st, 0); + active_pattern.active = 0; // consumed + } else if (dc->dev) { fz_matrix ctm = dvi_get_ctm(dc, st); fz_path *path = get_path(ctx, dc); fz_fill_path(ctx, dc->dev, path, 0, ctm, device_cs(ctx), - st->gs.colors.fill, 1.0, color_params); + st->gs.colors.fill, st->gs.fill_alpha, color_params); } drop_path(ctx, dc); break; case PDF_OP_f_star: - if (dc->dev) - { + if (active_pattern.active && !in_pattern_tile) { + render_pdf_pattern_fill(ctx, dc, st, 1); + active_pattern.active = 0; // consumed + } else if (dc->dev) { fz_matrix ctm = dvi_get_ctm(dc, st); fz_path *path = get_path(ctx, dc); fz_fill_path(ctx, dc->dev, path, 1, ctm, device_cs(ctx), - st->gs.colors.fill, 1.0, color_params); + st->gs.colors.fill, st->gs.fill_alpha, color_params); } drop_path(ctx, dc); break; @@ -969,7 +1147,7 @@ pdf_code(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, cursor_t get_stroke_state(ctx, st, &stst); fz_path *path = get_path(ctx, dc); fz_stroke_path(ctx, dc->dev, path, &stst, ctm, device_cs(ctx), - st->gs.colors.line, 1.0, color_params); + st->gs.colors.line, st->gs.stroke_alpha, color_params); } drop_path(ctx, dc); break; @@ -983,7 +1161,7 @@ pdf_code(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, cursor_t fz_path *path = get_path(ctx, dc); fz_closepath(ctx, path); fz_stroke_path(ctx, dc->dev, path, &stst, ctm, device_cs(ctx), - st->gs.colors.line, 1.0, color_params); + st->gs.colors.line, st->gs.stroke_alpha, color_params); } drop_path(ctx, dc); break; @@ -1034,89 +1212,431 @@ pdf_code(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, cursor_t val v[2]; vstack_get_arguments(ctx, stack, v, 2); st->gs.dash_len = val_array_length(ctx, stack, v[0]); - if (st->gs.dash_len > 4) st->gs.dash_len = 4; + if (st->gs.dash_len > 32) st->gs.dash_len = 32; for (int i = 0; i < st->gs.dash_len; ++i) st->gs.dash[i] = val_number(ctx, val_array_get(ctx, stack, v[0], i)); st->gs.dash_phase = val_number(ctx, v[1]); break; } - case PDF_OP_ri: - case PDF_OP_i: - case PDF_OP_gs: case PDF_OP_v: + { + fz_path *path = get_path(ctx, dc); + float c[4]; + fz_point pt = fz_currentpoint(ctx, path); + vstack_get_floats(ctx, stack, c, 4); + fz_curveto(ctx, path, pt.x, pt.y, c[0], c[1], c[2], c[3]); + break; + } case PDF_OP_y: + { + fz_path *path = get_path(ctx, dc); + float c[4]; + vstack_get_floats(ctx, stack, c, 4); + fz_curveto(ctx, path, c[0], c[1], c[2], c[3], c[2], c[3]); + break; + } + // -- Text operators (Phase 1) -- case PDF_OP_BT: + { + // PDF defaults for text state (Table 5.3 in PDF 1.7 spec) + st->gs.text.Tm = fz_identity; + st->gs.text.Tlm = fz_identity; + st->gs.text.char_space = 0; // Tc + st->gs.text.word_space = 0; // Tw + st->gs.text.scale = 1.0f; // Tz (100% = no scaling) + st->gs.text.leading = 0; // TL + st->gs.text.render = 0; // Tr (fill) + st->gs.text.rise = 0; // Ts + st->gs.text.in_text = 1; + break; + } case PDF_OP_ET: + { + st->gs.text.in_text = 0; + dvi_context_flush_text(ctx, dc, st); + break; + } case PDF_OP_Tc: + { + float c[1]; + vstack_get_floats(ctx, stack, c, 1); + st->gs.text.char_space = c[0]; + break; + } case PDF_OP_Tw: + { + float c[1]; + vstack_get_floats(ctx, stack, c, 1); + st->gs.text.word_space = c[0]; + break; + } case PDF_OP_Tz: + { + float c[1]; + vstack_get_floats(ctx, stack, c, 1); + st->gs.text.scale = c[0] / 100.0f; + break; + } case PDF_OP_TL: + { + float c[1]; + vstack_get_floats(ctx, stack, c, 1); + st->gs.text.leading = c[0]; + break; + } case PDF_OP_Tf: + { + val v[2]; + vstack_get_arguments(ctx, stack, v, 2); + const char *name = val_as_name(ctx, stack, v[0]); + float size = val_number(ctx, v[1]); + st->gs.text.font_size = size; + if (name) + { + size_t len = strlen(name); + if (len > 63) len = 63; + memcpy(st->gs.text.font_name, name, len); + st->gs.text.font_name[len] = 0; + } + break; + } case PDF_OP_Tr: + { + float c[1]; + vstack_get_floats(ctx, stack, c, 1); + st->gs.text.render = (int)c[0]; + break; + } case PDF_OP_Ts: + { + float c[1]; + vstack_get_floats(ctx, stack, c, 1); + st->gs.text.rise = c[0]; + break; + } case PDF_OP_Td: + { + float c[2]; + vstack_get_floats(ctx, stack, c, 2); + fz_matrix delta = fz_translate(c[0], c[1]); + st->gs.text.Tlm = fz_concat(delta, st->gs.text.Tlm); + st->gs.text.Tm = st->gs.text.Tlm; + break; + } case PDF_OP_TD: + { + float c[2]; + vstack_get_floats(ctx, stack, c, 2); + st->gs.text.leading = -c[1]; + fz_matrix delta = fz_translate(c[0], c[1]); + st->gs.text.Tlm = fz_concat(delta, st->gs.text.Tlm); + st->gs.text.Tm = st->gs.text.Tlm; + break; + } case PDF_OP_Tm: + { + float fmat[6]; + vstack_get_floats(ctx, stack, fmat, 6); + st->gs.text.Tlm.a = fmat[0]; st->gs.text.Tlm.b = fmat[1]; + st->gs.text.Tlm.c = fmat[2]; st->gs.text.Tlm.d = fmat[3]; + st->gs.text.Tlm.e = fmat[4]; st->gs.text.Tlm.f = fmat[5]; + st->gs.text.Tm = st->gs.text.Tlm; + break; + } case PDF_OP_T_star: + { + fz_matrix delta = fz_translate(0, -st->gs.text.leading); + st->gs.text.Tlm = fz_concat(delta, st->gs.text.Tlm); + st->gs.text.Tm = st->gs.text.Tlm; + break; + } case PDF_OP_Tj: + { + val v[1]; + vstack_get_arguments(ctx, stack, v, 1); + const char *str = val_as_string(ctx, stack, v[0]); + size_t slen = val_string_length(ctx, stack, v[0]); + fz_font *font = resolve_special_font(ctx, dc, st, st->gs.text.font_name); + show_special_text(ctx, dc, st, str, slen, font); + break; + } case PDF_OP_TJ: + { + val v[1]; + vstack_get_arguments(ctx, stack, v, 1); + int alen = val_array_length(ctx, stack, v[0]); + fz_font *font = resolve_special_font(ctx, dc, st, st->gs.text.font_name); + for (int i = 0; i < alen; i++) + { + val elem = val_array_get(ctx, stack, v[0], i); + if (val_is_string(elem)) + { + const char *str = val_string(ctx, stack, elem); + size_t slen = val_string_length(ctx, stack, elem); + show_special_text(ctx, dc, st, str, slen, font); + } + else if (val_is_number(elem)) + { + // Kerning adjustment: move text matrix by -kern/1000 * font_size + float kern = val_number(ctx, elem); + float tx = -kern / 1000.0f * st->gs.text.font_size * st->gs.text.scale; + st->gs.text.Tm = fz_pre_translate(st->gs.text.Tm, tx, 0); + } + } + break; + } case PDF_OP_squote: + { + // ' = T* + Tj + fz_matrix delta = fz_translate(0, -st->gs.text.leading); + st->gs.text.Tlm = fz_concat(delta, st->gs.text.Tlm); + st->gs.text.Tm = st->gs.text.Tlm; + val v[1]; + vstack_get_arguments(ctx, stack, v, 1); + const char *str = val_as_string(ctx, stack, v[0]); + size_t slen = val_string_length(ctx, stack, v[0]); + fz_font *font = resolve_special_font(ctx, dc, st, st->gs.text.font_name); + show_special_text(ctx, dc, st, str, slen, font); + break; + } case PDF_OP_dquote: - case PDF_OP_d0: - case PDF_OP_d1: - case PDF_OP_CS: - case PDF_OP_cs: - case PDF_OP_SC: - case PDF_OP_sc: - case PDF_OP_SCN: - case PDF_OP_scn: - case PDF_OP_sh: - case PDF_OP_Do: + { + // '' = set Tw/Tc + T* + Tj + // Get all 3 operands at once: aw, ac, string + val v[3]; + vstack_get_arguments(ctx, stack, v, 3); + st->gs.text.word_space = val_number(ctx, v[0]); + st->gs.text.char_space = val_number(ctx, v[1]); + fz_matrix delta = fz_translate(0, -st->gs.text.leading); + st->gs.text.Tlm = fz_concat(delta, st->gs.text.Tlm); + st->gs.text.Tm = st->gs.text.Tlm; + const char *str = val_as_string(ctx, stack, v[2]); + size_t slen = val_string_length(ctx, stack, v[2]); + fz_font *font = resolve_special_font(ctx, dc, st, st->gs.text.font_name); + show_special_text(ctx, dc, st, str, slen, font); + break; + } + + // No-ops: operators without visual effect. + // IMPORTANT: must consume operands from the stack to prevent corruption. case PDF_OP_MP: + { + val v[1]; + vstack_get_arguments(ctx, stack, v, 1); // tag name + break; + } case PDF_OP_DP: + { + val v[2]; + vstack_get_arguments(ctx, stack, v, 2); // tag name + properties + break; + } case PDF_OP_BMC: + { + val v[1]; + vstack_get_arguments(ctx, stack, v, 1); // tag name + break; + } case PDF_OP_BDC: + { + // BDC has 1 or 2 operands (tag + optional property dict) + // Try popping 2; if the second isn't a dict, it's fine + val v[2]; + vstack_get_arguments(ctx, stack, v, 2); // tag name + properties + break; + } case PDF_OP_EMC: + // EMC: 0 operands + break; + case PDF_OP_BX: case PDF_OP_EX: - default: - fprintf(stderr, "pdf unhandled op %s in:\n%.*s\n", pdf_op_name(op), - (int)(cur - cur0), cur0); + // Compatibility operators: 0 operands break; - case PDF_NONE: + case PDF_OP_ri: + { + val v[1]; + vstack_get_arguments(ctx, stack, v, 1); // rendering intent name break; - } - } while (op != PDF_NONE); - } - fz_always(ctx) - { - vstack_free(ctx, stack); - } - fz_catch(ctx) - { - return 0; - } - return 1; -} + } + case PDF_OP_i: + { + float c[1]; + vstack_get_floats(ctx, stack, c, 1); // flatness value + break; + } -static cursor_t parse_pdf_string(char *buf, char *end, cursor_t cur, cursor_t lim) -{ - if (buf >= end) - return cur; + case PDF_OP_d0: + { + float c[2]; + vstack_get_floats(ctx, stack, c, 2); // wx wy + break; + } + case PDF_OP_d1: + { + float c[6]; + vstack_get_floats(ctx, stack, c, 6); // wx wy llx lly urx ury + break; + } - int nesting = 1; - end -= 1; - while (cur < lim) - { - if (*cur == '(') - nesting += 1; - else if (*cur == ')') - { - nesting -= 1; - if (nesting == 0) - break; + // Color space operators: TikZ primarily uses direct color operators + // (rg/RG/g/G/k/K) which are already supported. + case PDF_OP_CS: + case PDF_OP_cs: + { + val v[1]; + vstack_get_arguments(ctx, stack, v, 1); // color space name + break; + } + case PDF_OP_SC: + { + float c[3]; + vstack_get_floats(ctx, stack, c, 3); + color_set_rgb(st->gs.colors.line, c[0], c[1], c[2]); + break; + } + case PDF_OP_sc: + { + float c[3]; + vstack_get_floats(ctx, stack, c, 3); + color_set_rgb(st->gs.colors.fill, c[0], c[1], c[2]); + break; + } + case PDF_OP_SCN: + { + // SCN operand count depends on current color space: + // 1 (Gray), 3 (RGB), 4 (CMYK), N+1 (Pattern name + components) + val array = vstack_get_values(ctx, stack); + int n = val_array_length(ctx, stack, array); + if (n >= 1 && val_is_number(val_array_get(ctx, stack, array, 0))) { + if (n == 1) { + color_set_gray(st->gs.colors.line, val_number(ctx, val_array_get(ctx, stack, array, 0))); + } else if (n == 3) { + color_set_rgb(st->gs.colors.line, + val_number(ctx, val_array_get(ctx, stack, array, 0)), + val_number(ctx, val_array_get(ctx, stack, array, 1)), + val_number(ctx, val_array_get(ctx, stack, array, 2))); + } else if (n == 4) { + color_set_cmyk(st->gs.colors.line, + val_number(ctx, val_array_get(ctx, stack, array, 0)), + val_number(ctx, val_array_get(ctx, stack, array, 1)), + val_number(ctx, val_array_get(ctx, stack, array, 2)), + val_number(ctx, val_array_get(ctx, stack, array, 3))); + } + } + break; + } + case PDF_OP_scn: + { + val array = vstack_get_values(ctx, stack); + int n = val_array_length(ctx, stack, array); + // Check if last operand is a pattern name (e.g., /pgfpatN) + if (n >= 1 && val_is_name(val_array_get(ctx, stack, array, n - 1))) { + const char *name = val_as_name(ctx, stack, val_array_get(ctx, stack, array, n - 1)); + if (name && memcmp(name, "pgfpat", 6) == 0) { + // Pattern fill: R G B /pgfpatN scn (uncolored) or /pgfpatN scn (colored) + int pi = pdf_pat_lookup(name); + if (pi >= 0) { + active_pattern.active = 1; + active_pattern.pat_index = pi; + // Extract RGB color for uncolored patterns - validate types + if (n >= 4 && + val_is_number(val_array_get(ctx, stack, array, 0)) && + val_is_number(val_array_get(ctx, stack, array, 1)) && + val_is_number(val_array_get(ctx, stack, array, 2))) { + active_pattern.color[0] = val_number(ctx, val_array_get(ctx, stack, array, 0)); + active_pattern.color[1] = val_number(ctx, val_array_get(ctx, stack, array, 1)); + active_pattern.color[2] = val_number(ctx, val_array_get(ctx, stack, array, 2)); + } else { + // Default to black for colored patterns or invalid color operands + active_pattern.color[0] = 0; + active_pattern.color[1] = 0; + active_pattern.color[2] = 0; + } + } + } + } else if (n >= 1 && val_is_number(val_array_get(ctx, stack, array, 0))) { + // Normal color — clear pattern + active_pattern.active = 0; + if (n == 1) { + color_set_gray(st->gs.colors.fill, val_number(ctx, val_array_get(ctx, stack, array, 0))); + } else if (n == 3) { + color_set_rgb(st->gs.colors.fill, + val_number(ctx, val_array_get(ctx, stack, array, 0)), + val_number(ctx, val_array_get(ctx, stack, array, 1)), + val_number(ctx, val_array_get(ctx, stack, array, 2))); + } else if (n == 4) { + color_set_cmyk(st->gs.colors.fill, + val_number(ctx, val_array_get(ctx, stack, array, 0)), + val_number(ctx, val_array_get(ctx, stack, array, 1)), + val_number(ctx, val_array_get(ctx, stack, array, 2)), + val_number(ctx, val_array_get(ctx, stack, array, 3))); + } + } + break; + } + + // Still pending implementation: consume operands to prevent stack leak + case PDF_OP_gs: + { + val v[1]; + vstack_get_arguments(ctx, stack, v, 1); // ExtGState name + break; + } + case PDF_OP_sh: + { + val v[1]; + vstack_get_arguments(ctx, stack, v, 1); // shading name + break; + } + case PDF_OP_Do: + { + val v[1]; + vstack_get_arguments(ctx, stack, v, 1); // XObject name + break; + } + default: + fprintf(stderr, "pdf unhandled op %s in:\n%.*s\n", pdf_op_name(op), + (int)(cur - cur0), cur0); + break; + + case PDF_NONE: + break; + } + } while (op != PDF_NONE); + } + fz_always(ctx) + { + vstack_free(ctx, stack); + } + fz_catch(ctx) + { + // Don't abort page rendering on unknown PDF operators + fprintf(stderr, "unhandled pdf content operator near: \"%.*s\"\n", + (int)(lim - cur), cur); + return 1; + } + return 1; +} + +static cursor_t parse_pdf_string(char *buf, char *end, cursor_t cur, cursor_t lim) +{ + if (buf >= end) + return cur; + + int nesting = 1; + end -= 1; + while (cur < lim) + { + if (*cur == '(') + nesting += 1; + else if (*cur == ')') + { + nesting -= 1; + if (nesting == 0) + break; } else if (*cur == '\\') { @@ -1135,6 +1655,1674 @@ static cursor_t parse_pdf_string(char *buf, char *end, cursor_t cur, cursor_t li return cur; } +#define MAX_SUBFUNC 8 + +typedef struct { + float c0[3], c1[3]; +} shade_subfunc; + +// Evaluate a stitching function at parameter t (0..1). +// nsub sub-functions partition the domain via nsub-1 bounds. +// Each sub-function i has C0[i], C1[i] and does linear interpolation +// across its sub-domain. +static void shade_eval_color(float out[3], int nsub, const shade_subfunc *sf, + const float *bounds, float t) +{ + if (t < 0) t = 0; if (t > 1) t = 1; + if (nsub <= 1) { + float s = t; + out[0] = sf[0].c0[0] + s * (sf[0].c1[0] - sf[0].c0[0]); + out[1] = sf[0].c0[1] + s * (sf[0].c1[1] - sf[0].c0[1]); + out[2] = sf[0].c0[2] + s * (sf[0].c1[2] - sf[0].c0[2]); + return; + } + // Find which sub-function covers t + int k = nsub - 1; // default: last segment + for (int i = 0; i < nsub - 1; i++) { + if (t < bounds[i]) { k = i; break; } + } + // Map t to sub-function's local [0,1] + float lo = (k == 0) ? 0.0f : bounds[k-1]; + float hi = (k == nsub - 1) ? 1.0f : bounds[k]; + float s = (t - lo) / (hi - lo); + out[0] = sf[k].c0[0] + s * (sf[k].c1[0] - sf[k].c0[0]); + out[1] = sf[k].c0[1] + s * (sf[k].c1[1] - sf[k].c0[1]); + out[2] = sf[k].c0[2] + s * (sf[k].c1[2] - sf[k].c0[2]); +} + +static void render_axial_shade_full(fz_context *ctx, dvi_context *dc, dvi_state *st, + float x0, float y0, float x1, float y1, + int nsub, const shade_subfunc *sf, const float *bounds, + fz_path *clip_path); +static void render_radial_shade(fz_context *ctx, dvi_context *dc, dvi_state *st, + float cx, float cy, float r0, float r1, + const shade_subfunc *sf, int nsub, const float *bounds, int nbounds); +static void render_axial_shade(fz_context *ctx, dvi_context *dc, dvi_state *st, + float x0, float y0, float x1, float y1, float c0[3], float c1[3], fz_path *clip_path); + +// Parse a PostScript shading pattern dictionary from raw PS content. +// Scans for /Coords [x0 y0 x1 y1], /C0 [r g b], /C1 [r g b], +// and /Bounds for stitching functions. +// Returns true if a shading was found and rendered natively. +static bool try_parse_ps_shading(fz_context *ctx, dvi_context *dc, dvi_state *st, + const char *p, const char *end) +{ + // Scan for /Coords [x0 y0 x1 y1] (axial, 4 vals) or + // /Coords [x0 y0 r0 x1 y1 r1] (radial, 6 vals) + float cx0=0, cy0=0, cx1=0, cy1=0, rad_r0=0, rad_r1=0; + int ncoords = 0; + bool has_coords = false; + { + const char *s = p; + while (s + 8 < end) { + if (memcmp(s, "/Coords", 7) == 0 && (s[7] == ' ' || s[7] == '\n' || s[7] == '[')) { + s += 7; + while (s < end && (*s == ' ' || *s == '\n')) s++; + if (s < end && *s == '[') { s++; + char tmp[64]; int ti; + float vals[6]; int vi = 0; + while (vi < 6 && s < end) { + while (s < end && (*s == ' ' || *s == '\n')) s++; + if (s >= end || *s == ']') break; + const char *ns = s; + while (ns < end && *ns != ' ' && *ns != '\n' && *ns != ']') ns++; + ti = ns - s; if (ti > 63) ti = 63; + memcpy(tmp, s, ti); tmp[ti] = 0; + vals[vi++] = strtof(tmp, NULL); + s = ns; + } + ncoords = vi; + if (vi >= 4) { + cx0=vals[0]; cy0=vals[1]; cx1=vals[2]; cy1=vals[3]; has_coords = true; + } + if (vi >= 6) { + rad_r0 = vals[2]; + rad_r1 = vals[5]; + } + } + break; + } + s++; + } + } + if (!has_coords) return false; + + // Collect sub-function C0/C1 values in order. + // Also parse /Bounds array for stitching function domain partition. + shade_subfunc subs[MAX_SUBFUNC]; + int nsub = 0; + float bounds[MAX_SUBFUNC - 1]; + int nbounds = 0; + { + const char *s = p; + while (s + 4 < end) { + bool is_c0 = (memcmp(s, "/C0", 3) == 0 && (s[3] == ' ' || s[3] == '\n' || s[3] == '[')); + bool is_c1 = (memcmp(s, "/C1", 3) == 0 && (s[3] == ' ' || s[3] == '\n' || s[3] == '[')); + if (is_c0 || is_c1) { + s += 3; + while (s < end && (*s == ' ' || *s == '\n')) s++; + if (s < end && *s == '[') { s++; + float vals[3]; int vi = 0; + while (vi < 3 && s < end) { + while (s < end && (*s == ' ' || *s == '\n')) s++; + if (s >= end || *s == ']') break; + const char *ns = s; + while (ns < end && *ns != ' ' && *ns != '\n' && *ns != ']') ns++; + int ti = ns - s; if (ti > 63) ti = 63; + char tmp[64]; memcpy(tmp, s, ti); tmp[ti] = 0; + vals[vi++] = strtof(tmp, NULL); + s = ns; + } + if (vi >= 3) { + if (is_c0) { + if (nsub < MAX_SUBFUNC) { + subs[nsub].c0[0]=vals[0]; subs[nsub].c0[1]=vals[1]; subs[nsub].c0[2]=vals[2]; + subs[nsub].c1[0]=vals[0]; subs[nsub].c1[1]=vals[1]; subs[nsub].c1[2]=vals[2]; + // If this is after the first sub-function, c1 of previous was already set. + // The previous sub's c1 is either still default or was set by a /C1 entry. + nsub++; + } + } else { + if (nsub > 0) { + subs[nsub-1].c1[0]=vals[0]; subs[nsub-1].c1[1]=vals[1]; subs[nsub-1].c1[2]=vals[2]; + } + } + } + } + } else if (memcmp(s, "/Bounds", 7) == 0 && (s[7]==' ' || s[7]=='\n' || s[7]=='[')) { + s += 7; + while (s < end && (*s == ' ' || *s == '\n')) s++; + if (s < end && *s == '[') { s++; + while (nbounds < MAX_SUBFUNC - 1 && s < end) { + while (s < end && (*s == ' ' || *s == '\n')) s++; + if (s >= end || *s == ']') break; + const char *ns = s; + while (ns < end && *ns != ' ' && *ns != '\n' && *ns != ']') ns++; + int ti = ns - s; if (ti > 63) ti = 63; + char tmp[64]; memcpy(tmp, s, ti); tmp[ti] = 0; + bounds[nbounds++] = strtof(tmp, NULL); + s = ns; + } + } + } else { + s++; + } + } + } + + // Normalise bounds from absolute to [0,1] range using the shading domain. + { + const char *s = p; + while (s + 8 < end) { + if (memcmp(s, "/Domain", 7) == 0 && (s[7]==' ' || s[7]=='\n' || s[7]=='[')) { + s += 7; + while (s < end && (*s == ' ' || *s == '\n')) s++; + if (s < end && *s == '[') { s++; + float d0=0, d1=100; + char tmp[64]; int ti; + const char *ns = s; + while (ns < end && *ns != ' ' && *ns != '\n' && *ns != ']') ns++; + ti = ns - s; if (ti > 63) ti = 63; + memcpy(tmp, s, ti); tmp[ti]=0; d0 = strtof(tmp, NULL); + s = ns; + while (s < end && (*s == ' ' || *s == '\n')) s++; + ns = s; + while (ns < end && *ns != ' ' && *ns != '\n' && *ns != ']') ns++; + ti = ns - s; if (ti > 63) ti = 63; + memcpy(tmp, s, ti); tmp[ti]=0; d1 = strtof(tmp, NULL); + float dr = (d1 != d0) ? d1 - d0 : 1.0f; + for (int i = 0; i < nbounds; i++) + bounds[i] = (bounds[i] - d0) / dr; + } + break; + } + s++; + } + } + + // Determine shading type: look for /ShadingType + int shade_type = 2; // default axial + { + const char *s = p; + while (s + 13 < end) { + if (memcmp(s, "/ShadingType", 12) == 0) { + s += 12; + while (s < end && (*s == ' ' || *s == '\n')) s++; + if (s < end && *s >= '0' && *s <= '9') { + shade_type = *s - '0'; + } + break; + } + s++; + } + } + + if (shade_type == 2) { + render_axial_shade_full(ctx, dc, st, cx0, cy0, cx1, cy1, + nsub, subs, nbounds > 0 ? bounds : NULL, get_path(ctx, dc)); + } else if (shade_type == 3) { + float cx=cx0, cy=cy0, r0=0, r1; + if (ncoords >= 6) { + r0 = rad_r0; + r1 = rad_r1; + } else { + r1 = (float)sqrt((cx1-cx0)*(cx1-cx0)+(cy1-cy0)*(cy1-cy0)); + } + if (nsub > 0) { + render_radial_shade(ctx, dc, st, cx, cy, r0, r1, + subs, nsub, bounds, nbounds > 0 ? bounds : NULL); + } + } else { + return false; + } + return true; +} + +// Simple wrapper for callers with a single C0/C1 pair. +static void render_axial_shade(fz_context *ctx, dvi_context *dc, dvi_state *st, + float x0, float y0, float x1, float y1, + float c0[3], float c1[3], fz_path *clip_path) +{ + shade_subfunc sf; + sf.c0[0]=c0[0]; sf.c0[1]=c0[1]; sf.c0[2]=c0[2]; + sf.c1[0]=c1[0]; sf.c1[1]=c1[1]; sf.c1[2]=c1[2]; + render_axial_shade_full(ctx, dc, st, x0, y0, x1, y1, 1, &sf, NULL, clip_path); +} + +// Render axial gradient with proper stitching function support. +static void render_axial_shade_full(fz_context *ctx, dvi_context *dc, dvi_state *st, + float x0, float y0, float x1, float y1, + int nsub, const shade_subfunc *sf, const float *bounds, + fz_path *clip_path) +{ + if (!dc->dev) return; + + fz_matrix ctm = dvi_get_ctm(dc, st); + int steps = 500; + + float dx = x1 - x0, dy = y1 - y0; + float len = sqrtf(dx * dx + dy * dy); + if (len < 0.001f) return; + + float px = -dy / len, py = dx / len; + float gx = dx / len, gy = dy / len; + + // Perpendicular half-width from path bounds + float hw = 200.0f; + if (clip_path) { + fz_rect r = fz_bound_path(ctx, clip_path, 0, fz_identity); + if (!fz_is_infinite_rect(r) && !fz_is_empty_rect(r)) { + float max_dist = 0; + float corners[4][2] = {{r.x0, r.y0}, {r.x0, r.y1}, {r.x1, r.y0}, {r.x1, r.y1}}; + for (int c = 0; c < 4; c++) { + float rx = corners[c][0] - x0, ry = corners[c][1] - y0; + float perp = fabsf(ry * dx - rx * dy) / len; + if (perp > max_dist) max_dist = perp; + } + hw = max_dist + 1.0f; + } + } + + float alpha = st->gs.fill_alpha; + bool use_group = (alpha < 0.999f); + if (use_group) { + fz_rect dev_bbox; + if (clip_path) { + fz_rect r = fz_bound_path(ctx, clip_path, 0, fz_identity); + dev_bbox = fz_transform_rect(r, ctm); + } else { + float corners[4][2] = { + {x0 + px * hw, y0 + py * hw}, {x0 - px * hw, y0 - py * hw}, + {x1 + px * hw, y1 + py * hw}, {x1 - px * hw, y1 - py * hw}}; + dev_bbox = fz_empty_rect; + for (int c = 0; c < 4; c++) { + fz_point pt = fz_transform_point_xy(corners[c][0], corners[c][1], ctm); + dev_bbox = fz_include_point_in_rect(dev_bbox, pt); + } + } + fz_begin_group(ctx, dc->dev, dev_bbox, NULL, 1, 0, 0, alpha); + } + + float step_size = len / steps; + float overlap = step_size * 0.5f; + float fill_a = use_group ? 1.0f : alpha; + for (int i = 0; i < steps; i++) + { + float t = (i + 0.5f) / steps; + float color[3]; + shade_eval_color(color, nsub, sf, bounds, t); + + float tv0 = (float)i / steps; + float tv1 = (float)(i + 1) / steps; + float cx = x0 + dx * tv0 - gx * overlap; + float cy = y0 + dy * tv0 - gy * overlap; + float nx = x0 + dx * tv1 + gx * overlap; + float ny = y0 + dy * tv1 + gy * overlap; + + fz_path *path = fz_new_path(ctx); + fz_moveto(ctx, path, cx + px * hw, cy + py * hw); + fz_lineto(ctx, path, nx + px * hw, ny + py * hw); + fz_lineto(ctx, path, nx - px * hw, ny - py * hw); + fz_lineto(ctx, path, cx - px * hw, cy - py * hw); + fz_closepath(ctx, path); + fz_fill_path(ctx, dc->dev, path, 0, ctm, device_cs(ctx), + color, fill_a, color_params); + fz_drop_path(ctx, path); + } + + if (use_group) + fz_end_group(ctx, dc->dev); +} + +// Render a simple radial gradient natively. +static void render_radial_shade(fz_context *ctx, dvi_context *dc, dvi_state *st, + float cx, float cy, float r0, float r1, + const shade_subfunc *sf, int nsub, const float *bounds, int nbounds) +{ + if (!dc->dev) return; + + fz_matrix ctm = dvi_get_ctm(dc, st); + int steps = 400; + float r_max = r1 > r0 ? r1 : r0; + float r_min = r0 < r1 ? r0 : r1; + + float alpha = st->gs.fill_alpha; + bool use_group = (alpha < 0.999f); + if (use_group) { + fz_rect r = {cx - r_max, cy - r_max, cx + r_max, cy + r_max}; + fz_rect dev_bbox = fz_transform_rect(r, ctm); + fz_begin_group(ctx, dc->dev, dev_bbox, NULL, 1, 0, 0, alpha); + } + float fill_a = use_group ? 1.0f : alpha; + + for (int i = 0; i < steps; i++) + { + float t_inner = (float)i / steps; + float t_outer = (float)(i + 1) / steps; + float ri = r_min + (r_max - r_min) * t_inner; + float ro = r_min + (r_max - r_min) * t_outer; + float t = (t_inner + t_outer) * 0.5f; + + float color[3]; + shade_eval_color(color, nsub, sf, bounds, t); + + // Thick ring via even-odd fill + fz_path *path = fz_new_path(ctx); + fz_moveto(ctx, path, cx + ro, cy); + fz_curveto(ctx, path, cx + ro, cy + ro * 0.552f, cx + ro * 0.552f, cy + ro, cx, cy + ro); + fz_curveto(ctx, path, cx - ro * 0.552f, cy + ro, cx - ro, cy + ro * 0.552f, cx - ro, cy); + fz_curveto(ctx, path, cx - ro, cy - ro * 0.552f, cx - ro * 0.552f, cy - ro, cx, cy - ro); + fz_curveto(ctx, path, cx + ro * 0.552f, cy - ro, cx + ro, cy - ro * 0.552f, cx + ro, cy); + fz_closepath(ctx, path); + fz_moveto(ctx, path, cx + ri, cy); + fz_curveto(ctx, path, cx + ri, cy - ri * 0.552f, cx + ri * 0.552f, cy - ri, cx, cy - ri); + fz_curveto(ctx, path, cx - ri * 0.552f, cy - ri, cx - ri, cy - ri * 0.552f, cx - ri, cy); + fz_curveto(ctx, path, cx - ri, cy + ri * 0.552f, cx - ri * 0.552f, cy + ri, cx, cy + ri); + fz_curveto(ctx, path, cx + ri * 0.552f, cy + ri, cx + ri, cy + ri * 0.552f, cx + ri, cy); + fz_closepath(ctx, path); + fz_fill_path(ctx, dc->dev, path, 1, ctm, device_cs(ctx), + color, fill_a, color_params); + fz_drop_path(ctx, path); + } + + if (use_group) + fz_end_group(ctx, dc->dev); +} + +// ---- PS (PostScript) command interpreter for PGF/TikZ ---- +// PGF/TikZ outputs PostScript drawing commands via \special{ps:: ...} +// We maintain a small value stack and a function dictionary. + +#define PS_STACK_MAX 32 +#define PS_FUNC_MAX 16 + +typedef struct { + char name[32]; + char body[256]; + int body_len; +} ps_func_def; + +static float ps_stack[PS_STACK_MAX]; +static int ps_sp = 0; +static ps_func_def ps_funcs[PS_FUNC_MAX]; +static int ps_func_count = 0; + +static void ps_push(float v) { if (ps_sp < PS_STACK_MAX) ps_stack[ps_sp++] = v; } +static float ps_pop() { return ps_sp > 0 ? ps_stack[--ps_sp] : 0.0f; } +static int ps_depth() { return ps_sp; } +static void ps_clear() { ps_sp = 0; } +void ps_state_reset() { + ps_sp = 0; + /* preserve ps_func_count and pdf_pats across pages */ + /* reset runtime pattern state */ + active_pattern.active = 0; + active_pattern.pat_index = -1; + in_pattern_tile = 0; +} + +// ---- PDF Pattern Table ---- +// Stores pattern definitions received via dvipdfmx/xetex specials: +// pdf:stream @pgfpatternobjectN (content) << /Type /Pattern ... >> +// Content is a PDF content stream (using m/l/c/S/f etc.) +#define PDF_PAT_MAX 16 +#define PDF_PAT_BODY 1024 + +typedef struct { + char obj_name[48]; // e.g., "@pgfpatternobject1" + char pat_name[32]; // e.g., "pgfpat1" (mapped via pdf:put) + char content[PDF_PAT_BODY]; // PDF content stream + int content_len; + float bbox[4]; // [llx lly urx ury] + float xstep, ystep; + float matrix[6]; // pattern matrix [a b c d e f] + int paint_type; // 1=colored, 2=uncolored +} pdf_pat_def; + +static pdf_pat_def pdf_pats[PDF_PAT_MAX]; +static int pdf_pat_count = 0; +// in_pattern_tile and active_pattern are defined earlier (before pdf_code) + +// Parse a float value from a string, advancing the pointer +static float parse_pat_float(const char **pp, const char *end) +{ + while (*pp < end && (**pp == ' ' || **pp == '\n' || **pp == '\r')) (*pp)++; + char tmp[64]; int ti = 0; + while (*pp < end && **pp != ' ' && **pp != '\n' && **pp != '\r' && + **pp != '/' && **pp != ']' && **pp != '>' && ti < 63) + tmp[ti++] = *(*pp)++; + tmp[ti] = 0; + return (ti > 0) ? strtof(tmp, NULL) : 0.0f; +} + +// Parse the dictionary part of a pdf:stream special for pattern attributes +static void +parse_pat_dict(pdf_pat_def *pat, const char *dict, int dict_len) +{ + const char *p = dict, *end = dict + dict_len; + pat->bbox[0] = 0; pat->bbox[1] = 0; pat->bbox[2] = 1; pat->bbox[3] = 1; + pat->xstep = 1; pat->ystep = 1; + pat->matrix[0] = 1; pat->matrix[1] = 0; pat->matrix[2] = 0; + pat->matrix[3] = 1; pat->matrix[4] = 0; pat->matrix[5] = 0; + pat->paint_type = 2; + + while (p < end) { + while (p < end && (*p == ' ' || *p == '\n' || *p == '\r')) p++; + if (p >= end) break; + if (p + 10 <= end && memcmp(p, "/PaintType", 10) == 0) { + p += 10; pat->paint_type = (int)parse_pat_float(&p, end); + } else if (p + 5 <= end && memcmp(p, "/BBox", 5) == 0) { + p += 5; while (p < end && *p != '[') p++; + if (p < end) { p++; + for (int i = 0; i < 4; i++) pat->bbox[i] = parse_pat_float(&p, end); + while (p < end && *p != ']') p++; if (p < end) p++; + } + } else if (p + 6 <= end && memcmp(p, "/XStep", 6) == 0) { + p += 6; pat->xstep = parse_pat_float(&p, end); + } else if (p + 6 <= end && memcmp(p, "/YStep", 6) == 0) { + p += 6; pat->ystep = parse_pat_float(&p, end); + } else if (p + 7 <= end && memcmp(p, "/Matrix", 7) == 0) { + p += 7; while (p < end && *p != '[') p++; + if (p < end) { p++; + for (int i = 0; i < 6; i++) pat->matrix[i] = parse_pat_float(&p, end); + while (p < end && *p != ']') p++; if (p < end) p++; + } + } else { p++; } + } +} + +// Store a pattern from a pdf:stream special. +static void +pdf_pat_define(const char *obj_name, int onl, + const char *content, int cl, + const char *dict, int dl) +{ + if (!obj_name || onl < 18) return; + const char *check = obj_name; + if (*check == '@') check++; + if (memcmp(check, "pgfpatternobject", 16) != 0) return; + + pdf_pat_def *pat = NULL; + for (int i = 0; i < pdf_pat_count; i++) { + if ((int)strlen(pdf_pats[i].obj_name) == onl && + memcmp(pdf_pats[i].obj_name, obj_name, onl) == 0) { + pat = &pdf_pats[i]; break; + } + } + if (!pat) { + if (pdf_pat_count >= PDF_PAT_MAX) { + fprintf(stderr, "Warning: PDF pattern table overflow (max %d patterns). Pattern '%.*s' ignored.\n", + PDF_PAT_MAX, onl, obj_name); + return; + } + pat = &pdf_pats[pdf_pat_count++]; + } + int nl = (onl > 47) ? 47 : onl; + memcpy(pat->obj_name, obj_name, nl); pat->obj_name[nl] = 0; + pat->pat_name[0] = 0; + int sl = (cl > PDF_PAT_BODY - 1) ? PDF_PAT_BODY - 1 : cl; + memcpy(pat->content, content, sl); pat->content[sl] = 0; + pat->content_len = sl; + parse_pat_dict(pat, dict, dl); + fprintf(stderr, "[pattern] pdf_pat_define: obj='%s' bbox=[%.1f %.1f %.1f %.1f] " + "step=(%.1f,%.1f) paint_type=%d content_len=%d\n", + pat->obj_name, pat->bbox[0], pat->bbox[1], pat->bbox[2], pat->bbox[3], + pat->xstep, pat->ystep, pat->paint_type, pat->content_len); +} + +// Map /pgfpatN to @pgfpatternobjectN (from pdf:put @pgfpatterns << ... >>) +static void +pdf_pat_map_name(const char *pat_name, int pnl, + const char *obj_name, int onl) +{ + for (int i = 0; i < pdf_pat_count; i++) { + if ((int)strlen(pdf_pats[i].obj_name) == onl && + memcmp(pdf_pats[i].obj_name, obj_name, onl) == 0) { + int ml = (pnl > 31) ? 31 : pnl; + memcpy(pdf_pats[i].pat_name, pat_name, ml); + pdf_pats[i].pat_name[ml] = 0; + fprintf(stderr, "[pattern] pdf_pat_map_name: '%s' -> '%s'\n", + pdf_pats[i].pat_name, pdf_pats[i].obj_name); + return; + } + } +} + +// Look up a pattern by its /pgfpatN name. Returns index or -1. +static int pdf_pat_lookup(const char *pat_name) +{ + for (int i = 0; i < pdf_pat_count; i++) + if (strcmp(pdf_pats[i].pat_name, pat_name) == 0) return i; + return -1; +} + + + +// Forward declaration +static void ps_define_func(const char *name, int nl, const char *body, int bl); +static const char *ps_lookup_func(const char *name); + +// Parse PS function definitions from a string without executing any commands. +// Handles both "def" and "bind def". Used to pre-scan "!" /pgf specials. +static void ps_parse_defs(const char *p, const char *end) +{ + while (p < end) + { + while (p < end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) p++; + if (p >= end) break; + + if (*p != '/') { + // Skip non-definition tokens + while (p < end && *p != ' ' && *p != '\n' && *p != '\r' && *p != '\t' && *p != '/') p++; + continue; + } + p++; // skip '/' + const char *ns = p; + while (p < end && *p != '{' && *p != ' ' && *p != '\n' && *p != '\r' && *p != '\t') p++; + int nl = p - ns; + while (p < end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) p++; + if (p >= end || *p != '{') continue; + p++; // skip '{' + const char *bs = p; + int depth = 1; + while (p < end && depth > 0) { + if (*p == '{') depth++; else if (*p == '}') depth--; + p++; + } + int bl = p - bs - 1; + while (p < end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) p++; + // Handle both "bind def" and "def" + bool is_bind = false; + if (p + 4 <= end && memcmp(p, "bind", 4) == 0) { + is_bind = true; + p += 4; + while (p < end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) p++; + } + if (p + 3 <= end && memcmp(p, "def", 3) == 0) { + p += 3; + if (nl > 0 && bl >= 0) { + // Always overwrite: with function preservation across pages, + // we MUST allow ! specials to clear stale per-element colors + // (pgffc/pgfsc) and redefine library functions after reset. + ps_define_func(ns, nl, bs, bl); + } + } + } +} + +static void ps_define_func(const char *name, int nl, const char *body, int bl) +{ + // Overwrite existing definition with the same name + for (int i = 0; i < ps_func_count; i++) { + if ((int)strlen(ps_funcs[i].name) == nl && memcmp(ps_funcs[i].name, name, nl) == 0) { + if (bl > 255) bl = 255; + memcpy(ps_funcs[i].body, body, bl); + ps_funcs[i].body[bl] = 0; + ps_funcs[i].body_len = bl; + return; + } + } + // New definition + if (ps_func_count >= PS_FUNC_MAX) return; + ps_func_def *f = &ps_funcs[ps_func_count++]; + if (nl > 31) nl = 31; memcpy(f->name, name, nl); f->name[nl] = 0; + if (bl > 255) bl = 255; memcpy(f->body, body, bl); f->body[bl] = 0; + f->body_len = bl; +} + +static const char *ps_lookup_func(const char *name) +{ + for (int i = 0; i < ps_func_count; i++) + if (strcmp(ps_funcs[i].name, name) == 0) return ps_funcs[i].body; + return NULL; +} + +enum { PS_COLOR_FILL=0, PS_COLOR_STROKE=1, PS_COLOR_BOTH=2 }; +static void ps_exec_body(fz_context *ctx, dvi_context *dc, dvi_state *st, + const char *body, int body_len, int color_target); + +// Render a PDF tiling pattern fill using MuPDF's native tiling API. +// Called from pdf_code fill operators (f/F/b/B) when active_pattern is set. +// Clips to the fill path, then uses fz_begin_tile/fz_end_tile to let MuPDF +// handle tile repetition automatically — we only draw tile content once. +static void +render_pdf_pattern_fill(fz_context *ctx, dvi_context *dc, dvi_state *st, int even_odd) +{ + if (!dc->dev || !dc->path || active_pattern.pat_index < 0) return; + + pdf_pat_def *pat = &pdf_pats[active_pattern.pat_index]; + if (pat->content_len <= 0) return; + + // Validate xstep/ystep — use bbox dimensions as fallback + float xstep = pat->xstep > 0 ? pat->xstep : (pat->bbox[2] - pat->bbox[0]); + float ystep = pat->ystep > 0 ? pat->ystep : (pat->bbox[3] - pat->bbox[1]); + if (xstep <= 0 || ystep <= 0) return; + + fprintf(stderr, "[pattern] render_pdf_pattern_fill: pat='%s' bbox=[%.1f %.1f %.1f %.1f] " + "step=(%.1f,%.1f) matrix=[%.2f %.2f %.2f %.2f %.2f %.2f] content_len=%d paint_type=%d\n", + pat->pat_name, pat->bbox[0], pat->bbox[1], pat->bbox[2], pat->bbox[3], + xstep, ystep, + pat->matrix[0], pat->matrix[1], pat->matrix[2], pat->matrix[3], + pat->matrix[4], pat->matrix[5], pat->content_len, pat->paint_type); + + in_pattern_tile = 1; + + // Save graphics state + float saved_fill[3], saved_line[3]; + float saved_fa, saved_sa, saved_lw; + fz_matrix saved_ctm = st->gs.ctm; + int saved_h = st->gs.h, saved_v = st->gs.v; + int saved_clip_depth = st->gs.clip_depth; + memcpy(saved_fill, st->gs.colors.fill, sizeof(saved_fill)); + memcpy(saved_line, st->gs.colors.line, sizeof(saved_line)); + saved_fa = st->gs.fill_alpha; + saved_sa = st->gs.stroke_alpha; + saved_lw = st->gs.line_width; + + // Set pattern color for drawing (uncolored patterns use the color from scn) + if (pat->paint_type == 2) { + color_set_rgb(st->gs.colors.fill, + active_pattern.color[0], active_pattern.color[1], active_pattern.color[2]); + color_set_rgb(st->gs.colors.line, + active_pattern.color[0], active_pattern.color[1], active_pattern.color[2]); + } + + // Detach the fill path — we use it for clipping, not for tile content + fz_path *clip_path = dc->path; + dc->path = NULL; + fz_matrix ctm = dvi_get_ctm(dc, st); + + // Clip to the fill path shape + fz_clip_path(ctx, dc->dev, clip_path, even_odd, ctm, fz_infinite_rect); + + // Build pattern matrix: pattern space → user space → device space + fz_matrix pat_mat; + pat_mat.a = pat->matrix[0]; pat_mat.b = pat->matrix[1]; + pat_mat.c = pat->matrix[2]; pat_mat.d = pat->matrix[3]; + pat_mat.e = pat->matrix[4]; pat_mat.f = pat->matrix[5]; + fz_matrix tile_ctm = fz_concat(pat_mat, ctm); + + // Area to be tiled (fill path bounds in device coordinates) + fz_rect area = fz_bound_path(ctx, clip_path, NULL, ctm); + + // Tile view rectangle (pattern BBox in pattern space) + fz_rect view; + view.x0 = pat->bbox[0]; view.y0 = pat->bbox[1]; + view.x1 = pat->bbox[2]; view.y1 = pat->bbox[3]; + + fz_try(ctx) { + // Use MuPDF's native tiling: draw tile content once, MuPDF repeats it. + // - area: region to cover (device space, after ctm) + // - view: one tile's BBox (pattern space) + // - xstep/ystep: tile spacing (pattern space) + // - tile_ctm: transform from pattern space to device space + fz_begin_tile(ctx, dc->dev, area, view, xstep, ystep, tile_ctm); + + // Set CTM for tile content — pdf_code uses dvi_get_ctm(dc, st) + st->gs.ctm = tile_ctm; + st->gs.h = st->registers.h; + st->gs.v = st->registers.v; + + // Execute tile content stream ONCE — MuPDF tiles it automatically + fz_path *saved_path = dc->path; + dc->path = NULL; + pdf_code(ctx, dc, st, pat->content, pat->content + pat->content_len); + if (dc->path) { drop_path(ctx, dc); } + dc->path = saved_path; + + fz_end_tile(ctx, dc->dev); + } + fz_always(ctx) { + // Pop the clip we pushed above + fz_pop_clip(ctx, dc->dev); + + // Restore the fill path for caller + dc->path = clip_path; + + // Restore graphics state + st->gs.ctm = saved_ctm; + st->gs.h = saved_h; + st->gs.v = saved_v; + st->gs.clip_depth = saved_clip_depth; + memcpy(st->gs.colors.fill, saved_fill, sizeof(saved_fill)); + memcpy(st->gs.colors.line, saved_line, sizeof(saved_line)); + st->gs.fill_alpha = saved_fa; + st->gs.stroke_alpha = saved_sa; + st->gs.line_width = saved_lw; + + in_pattern_tile = 0; + } + fz_catch(ctx) { + // Don't rethrow — pattern rendering failure should not abort the page + fprintf(stderr, "render_pdf_pattern_fill: error rendering pattern tile\n"); + } +} + + +static bool +ps_code(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, cursor_t lim) +{ + const char *p = cur, *end = lim; + + // Track whether a fill/stroke operation was performed that didn't + // drop the path. pgffill leaves the path so that pgfstr can stroke + // it in a \filldraw. But when the special ends and no pgfstr + // followed, we must drop the path ourselves. + bool rendered = false; + + while (p < end) + { + while (p < end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) p++; + if (p >= end) break; + + // Handle function definition: /name{body}def + if (*p == '/') + { + p++; + const char *ns = p; + while (p < end && *p != '{' && *p != ' ') p++; + int nl = p - ns; + while (p < end && *p == ' ') p++; + if (p < end && *p == '{') { + p++; + const char *bs = p; + int depth = 1; + while (p < end && depth > 0) { + if (*p == '{') depth++; else if (*p == '}') depth--; + p++; + } + int bl = p - bs - 1; // without closing } + while (p < end && *p == ' ') p++; + // Support both "def" and "bind def" + bool is_bind = false; + if (p + 4 <= end && memcmp(p, "bind", 4) == 0) { + is_bind = true; + p += 4; + while (p < end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) p++; + } + if (p + 3 <= end && memcmp(p, "def", 3) == 0) { + p += 3; + ps_define_func(ns, nl, bs, bl); + // Immediately execute color function bodies so that colors are + // set even if later ps_lookup_func fails (e.g. due to corruption). + if (nl == 5 && memcmp(ns, "pgffc", 5) == 0) { + // Pattern bodies contain pgfpat whose makepattern/setcolor + // operators are not implemented. Extract color directly. + if (bl >= 6) { + int found = 0; + for (int i = 0; i <= bl - 6; i++) + if (memcmp(bs + i, "pgfpat", 6) == 0) { found = 1; break; } + if (found) { + float pr = 1, pg = 1, pb = 1; + sscanf(bs, "%f %f %f", &pr, &pg, &pb); + color_set_rgb(st->gs.colors.fill, pr, pg, pb); + } else { + ps_exec_body(ctx, dc, st, bs, bl, PS_COLOR_FILL); + } + } else { + ps_exec_body(ctx, dc, st, bs, bl, PS_COLOR_FILL); + } + ps_clear(); + } else if (nl == 5 && memcmp(ns, "pgfsc", 5) == 0) { + ps_exec_body(ctx, dc, st, bs, bl, PS_COLOR_STROKE); + ps_clear(); + } + continue; + } + } + continue; + } + + // Parse token + const char *ts = p; + while (p < end && *p != ' ' && *p != '\n' && *p != '\r' && *p != '\t') p++; + int tl = p - ts; + if (tl == 0) continue; + + char tmp[64]; + if (tl > 63) tl = 63; + memcpy(tmp, ts, tl); tmp[tl] = 0; + + // Try as number (strip [ and ] from PS array syntax like [1.0 or -4.08]) + char *num_start = tmp; + if (*num_start == '[') num_start++; + int num_len = strlen(num_start); + if (num_len > 0 && num_start[num_len-1] == ']') { + num_start[num_len-1] = 0; + num_len--; + } + char *ep; + float fv = strtof(num_start, &ep); + if (ep == num_start + num_len && num_len > 0 && *num_start != '/') + { + ps_push(fv); + continue; + } + + // --- Command dispatch --- + // PS array markers (standalone [ and ]) + if (tmp[0] == '[' && tmp[1] == 0) continue; + if (tmp[0] == ']' && tmp[1] == 0) continue; + // Path building + if (strcmp(tmp, "moveto") == 0) { + if (ps_depth() >= 2) { float y=ps_pop(), x=ps_pop(); fz_moveto(ctx, get_path(ctx,dc), x, y); } + } + else if (strcmp(tmp, "lineto") == 0) { + if (ps_depth() >= 2) { float y=ps_pop(), x=ps_pop(); fz_lineto(ctx, get_path(ctx,dc), x, y); } + } + else if (strcmp(tmp, "curveto") == 0) { + if (ps_depth() >= 6) { + float y3=ps_pop(),x3=ps_pop(), y2=ps_pop(),x2=ps_pop(), y1=ps_pop(),x1=ps_pop(); + fz_curveto(ctx, get_path(ctx,dc), x1,y1, x2,y2, x3,y3); + } + } + else if (strcmp(tmp, "closepath") == 0) { + fz_closepath(ctx, get_path(ctx,dc)); + } + else if (strcmp(tmp, "newpath") == 0) { + drop_path(ctx, dc); + } + // PGF fill / stroke + // IMPORTANT: fill does NOT drop the path — the same path is + // often stroked by a subsequent pgfstr. Path is dropped at + // pgfstr, newpath, or pgfc. + else if (strcmp(tmp, "pgffill") == 0) { + const char *b = ps_lookup_func("pgffc"); + if (b && *b) { + ps_exec_body(ctx, dc, st, b, strlen(b), PS_COLOR_FILL); + } + if (dc->dev) { + float *fc = st->gs.colors.fill; + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_fill_path(ctx, dc->dev, get_path(ctx,dc), 0, ctm, + device_cs(ctx), fc, st->gs.fill_alpha, color_params); + } + rendered = true; + // Do NOT drop path — pgfstr may follow within the same special + } + else if (strcmp(tmp, "pgfstr") == 0) { + const char *b = ps_lookup_func("pgfsc"); + if (b && *b) ps_exec_body(ctx, dc, st, b, strlen(b), PS_COLOR_STROKE); + // When pgfsc is empty, keep current line color (set by inline + // setgray/setrgbcolor/setcmykcolor commands) + float *lc = st->gs.colors.line; + if (dc->dev) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_stroke_state sst; + get_stroke_state(ctx, st, &sst); + fz_stroke_path(ctx, dc->dev, get_path(ctx,dc), &sst, ctm, + device_cs(ctx), lc, st->gs.stroke_alpha, color_params); + } + drop_path(ctx, dc); // stroke is final, drop path + rendered = true; + } + // Graphics state + else if (strcmp(tmp, "gsave") == 0 || strcmp(tmp, "save") == 0) { + if (st->gs_stack.depth < st->gs_stack.limit) { + st->gs_stack.base[st->gs_stack.depth] = st->gs; + st->gs_stack.depth += 1; + } + } + else if (strcmp(tmp, "grestore") == 0 || strcmp(tmp, "restore") == 0) { + if (st->gs_stack.depth > 0) { + int cd0 = st->gs.clip_depth; + st->gs_stack.depth -= 1; + st->gs = st->gs_stack.base[st->gs_stack.depth]; + if (dc->dev) for (int i = st->gs.clip_depth; i < cd0; ++i) fz_pop_clip(ctx, dc->dev); + } + } + // Line width + else if (strcmp(tmp, "pgfw") == 0 || strcmp(tmp, "setlinewidth") == 0) { + if (ps_depth() >= 1) st->gs.line_width = ps_pop(); + } + // Line cap / join + else if (strcmp(tmp, "setlinecap") == 0) { + if (ps_depth() >= 1) st->gs.line_caps = (int)ps_pop(); + } + else if (strcmp(tmp, "setlinejoin") == 0) { + if (ps_depth() >= 1) st->gs.line_join = (int)ps_pop(); + } + // Dash pattern + else if (strcmp(tmp, "setdash") == 0) { + if (ps_depth() >= 1) { + // Pop the dash phase (top of stack) + float ph = ps_pop(); + st->gs.dash_phase = ph; + // Remaining values on the stack are the dash array + // (pushed in order by PS interpreter from [a b c ...]) + int n = ps_depth(); + st->gs.dash_len = n; + if (n > 32) n = 32; + for (int i = 0; i < n; i++) { + st->gs.dash[i] = ps_stack[ps_sp - n + i]; + } + ps_clear(); // consume all array values + } + } + // Inline PS color operators: set the "current color" in PostScript, + // which affects both fill and stroke. We apply them to both fill + // and line colors so that subsequent pgfstr/pgffill use the right + // color even when pgfsc/pgffc are not redefined. + else if (strcmp(tmp, "setgray") == 0) { + if (ps_depth() >= 1) { + float g = ps_pop(); + color_set_gray(st->gs.colors.fill, g); + color_set_gray(st->gs.colors.line, g); + } + } + else if (strcmp(tmp, "setrgbcolor") == 0) { + if (ps_depth() >= 3) { + float b = ps_pop(), g = ps_pop(), r = ps_pop(); + color_set_rgb(st->gs.colors.fill, r, g, b); + color_set_rgb(st->gs.colors.line, r, g, b); + } + } + else if (strcmp(tmp, "setcmykcolor") == 0) { + if (ps_depth() >= 4) { + float k = ps_pop(), y = ps_pop(), m = ps_pop(), c = ps_pop(); + color_set_cmyk(st->gs.colors.fill, c, m, y, k); + color_set_cmyk(st->gs.colors.line, c, m, y, k); + } + } + // PGF pgfe: default rectangle (height, width, x, y) from PS stack + // PGF always redefines pgfe for non-rectangle shapes via other PS commands + else if (strcmp(tmp, "pgfe") == 0) { + if (ps_depth() >= 4) { + float y=ps_pop(), x=ps_pop(), w=ps_pop(), h=ps_pop(); + // Args: bottom→top [height, width, x_start, y_start] + fz_rectto(ctx, get_path(ctx, dc), x, y, x + w, y + h); + } + } + // PS << dictionary start — used by PGF for shading patterns. + // Parse the following dictionary content for shading parameters + // and render natively via try_parse_ps_shading. + else if (strcmp(tmp, "<<") == 0) { + if (try_parse_ps_shading(ctx, dc, st, p, end)) { + drop_path(ctx, dc); + ps_clear(); + rendered = true; + break; + } + } + // PS concat: [a b c d e f] concat — concatenate matrix to CTM + // This is how PGF/dvips driver applies translations and transformations. + // PS coordinates are relative to the page top-left (Y-down), + // device coordinates are relative to page bottom-left (Y-up). + // Conversion: device_y = page_height - 72 - ps_y + else if (strcmp(tmp, "concat") == 0) { + if (ps_depth() >= 6) { + float f=ps_pop(), e=ps_pop(), d=ps_pop(), c=ps_pop(), b=ps_pop(), a=ps_pop(); + fz_matrix mat; + mat.a = a; mat.b = b; mat.c = c; + mat.d = d; mat.e = e; mat.f = f; + // Chain mat to dvi_get_ctm(dc, st) which includes the TeX + // page position (reg.h/reg.v relative to gs.h/gs.v) applied + // to the current CTM. This preserves the TeX positioning + // that would otherwise be lost when gs.h/gs.v are zeroed. + fz_matrix ctm_before = dvi_get_ctm(dc, st); + st->gs.ctm = fz_concat(mat, ctm_before); + st->gs.h = st->registers.h; + st->gs.v = st->registers.v; + ps_clear(); + } + } + // PGF opacity commands + else if (strcmp(tmp, ".pgfsetfillopacityalpha") == 0) { + if (ps_depth() >= 1) st->gs.fill_alpha = ps_pop(); + } + else if (strcmp(tmp, ".pgfsetstrokeopacityalpha") == 0) { + if (ps_depth() >= 1) st->gs.stroke_alpha = ps_pop(); + } + // PS stack operations + else if (strcmp(tmp, "dup") == 0) { + if (ps_depth() >= 1) { float v = ps_pop(); ps_push(v); ps_push(v); } + } + else if (strcmp(tmp, "pop") == 0) { + ps_pop(); // discard top of stack + } + else if (strcmp(tmp, "exch") == 0) { + if (ps_depth() >= 2) { float a=ps_pop(), b=ps_pop(); ps_push(a); ps_push(b); } + } + else if (strcmp(tmp, "neg") == 0) { + if (ps_depth() >= 1) { float v = ps_pop(); ps_push(-v); } + } + // PS currentpoint: push current path point (x,y) onto stack + else if (strcmp(tmp, "currentpoint") == 0) { + fz_point pt = fz_currentpoint(ctx, get_path(ctx, dc)); + ps_push(pt.x); ps_push(pt.y); + } + // PS translate: tx ty translate — apply to full CTM (incl. TeX position) + else if (strcmp(tmp, "translate") == 0) { + if (ps_depth() >= 2) { + float ty=ps_pop(), tx=ps_pop(); + st->gs.ctm = fz_pre_translate(dvi_get_ctm(dc, st), tx, ty); + st->gs.h = st->registers.h; + st->gs.v = st->registers.v; + } + } + // PS scale: sx sy scale — apply to full CTM (incl. TeX position) + else if (strcmp(tmp, "scale") == 0) { + if (ps_depth() >= 2) { + float sy=ps_pop(), sx=ps_pop(); + fz_matrix before = dvi_get_ctm(dc, st); + st->gs.ctm = fz_pre_scale(before, sx, sy); + st->gs.h = st->registers.h; + st->gs.v = st->registers.v; + } + } + // PS rotate: angle rotate — apply to full CTM (incl. TeX position) + else if (strcmp(tmp, "rotate") == 0) { + if (ps_depth() >= 1) { + float angle = ps_pop(); + st->gs.ctm = fz_pre_rotate(dvi_get_ctm(dc, st), angle); + st->gs.h = st->registers.h; + st->gs.v = st->registers.v; + } + } + // PGF /a function: pops x,y, does moveto (initializes path start) + else if (strcmp(tmp, "a") == 0) { + if (ps_depth() >= 2) { + float y=ps_pop(), x=ps_pop(); + fz_moveto(ctx, get_path(ctx, dc), x, y); + } + } + else if (strcmp(tmp, "rlineto") == 0) { + if (ps_depth() >= 2) { + float y=ps_pop(), x=ps_pop(); + fz_path *p = get_path(ctx, dc); + fz_point cp = fz_currentpoint(ctx, p); + fz_lineto(ctx, p, cp.x + x, cp.y + y); + } + } + // PS clip operators: W/W* (PDF names), clip/eoclip (PS names) + // Also handle {clip} {eoclip} — PS code blocks in PGF mask expression + else if (strcmp(tmp, "W") == 0 || strcmp(tmp, "W*") == 0 || + strcmp(tmp, "clip") == 0 || strcmp(tmp, "eoclip") == 0 || + strcmp(tmp, "{clip}") == 0 || strcmp(tmp, "{eoclip}") == 0) { + int eofill = (strcmp(tmp, "W*") == 0 || strcmp(tmp, "eoclip") == 0 || + strcmp(tmp, "{eoclip}") == 0) ? 1 : 0; + if (dc->dev && dc->path) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_clip_path(ctx, dc->dev, dc->path, eofill, ctm, + fz_infinite_rect); + st->gs.clip_depth += 1; + } + drop_path(ctx, dc); + } + else if (strcmp(tmp, "get") == 0 || strcmp(tmp, "ifelse") == 0) { + // PS dict/array get and conditional — not needed, consume 2 values + if (ps_depth() >= 2) { ps_pop(); ps_pop(); } + } + else if (strcmp(tmp, "/pgfsmaskinplace") == 0) { + // PGF soft mask placeholder — no-op for now + } + // PGF dvips stroke/fill aliases + else if (strcmp(tmp, "pgfs") == 0 || strcmp(tmp, "pgfS") == 0) { + // pgfs = PGF stroke (dvips alias for pgfstr) + const char *b = ps_lookup_func("pgfsc"); + if (b && *b) ps_exec_body(ctx, dc, st, b, strlen(b), PS_COLOR_STROKE); + // When pgfsc is empty, keep current line color + if (dc->dev) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_stroke_state sst; + get_stroke_state(ctx, st, &sst); + fz_stroke_path(ctx, dc->dev, get_path(ctx,dc), &sst, ctm, + device_cs(ctx), st->gs.colors.line, st->gs.stroke_alpha, color_params); + } + drop_path(ctx, dc); + rendered = true; + } + else if (strcmp(tmp, "pgfr") == 0 || strcmp(tmp, "pgfR") == 0) { + // pgfr = PGF fill (dvips alias for pgffill) + const char *b = ps_lookup_func("pgffc"); + if (b && *b) { + ps_exec_body(ctx, dc, st, b, strlen(b), PS_COLOR_FILL); + } + if (dc->dev) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_fill_path(ctx, dc->dev, get_path(ctx,dc), 0, ctm, + device_cs(ctx), st->gs.colors.fill, st->gs.fill_alpha, color_params); + } + rendered = true; + // Do NOT drop path — pgfstr may follow within the same special + } + // PGF cleanup / end markers + else if (strcmp(tmp, "pgfc") == 0 || strcmp(tmp, "pgfo") == 0) { /* no-op */ } + // PGF shading function invocations — intercept for native rendering. + // These are called via ps:: specials with params on the PS stack. + // We handle them before the general ps_lookup_func fallback because + // ps_exec_body cannot execute the shading PS operators natively, + // which would leave the path filled with the default (black) color. + else if (strcmp(tmp, "pgfHrgb") == 0 || strcmp(tmp, "pgfVrgb") == 0 || + strcmp(tmp, "pgfArgb") == 0) { + // Axial RGB: sx sy ex ey R1 G1 B1 R2 G2 B2 depth (11 params) + if (ps_depth() >= 11) { + float depth = ps_pop(); + float b2=ps_pop(), g2=ps_pop(), r2=ps_pop(); + float b1=ps_pop(), g1=ps_pop(), r1=ps_pop(); + float ey=ps_pop(), ex=ps_pop(), sy=ps_pop(), sx=ps_pop(); + float c0[3] = {r1, g1, b1}; + float c1[3] = {r2, g2, b2}; + (void)depth; + render_axial_shade(ctx, dc, st, sx, sy, ex, ey, c0, c1, NULL); + ps_clear(); + rendered = true; + } + } + else if (strcmp(tmp, "pgfHcmyk") == 0 || strcmp(tmp, "pgfVcmyk") == 0 || + strcmp(tmp, "pgfAcmyk") == 0) { + // Axial CMYK: sx sy ex ey C1 M1 Y1 K1 C2 M2 Y2 K2 depth (13 params) + if (ps_depth() >= 13) { + float depth = ps_pop(); + float k2=ps_pop(), y2=ps_pop(), m2=ps_pop(), c2_c=ps_pop(); + float k1=ps_pop(), y1=ps_pop(), m1=ps_pop(), c1_c=ps_pop(); + float ey=ps_pop(), ex=ps_pop(), sy=ps_pop(), sx=ps_pop(); + (void)depth; + float c0[3], c1[3]; + color_set_cmyk(c0, c1_c, m1, y1, k1); + color_set_cmyk(c1, c2_c, m2, y2, k2); + render_axial_shade(ctx, dc, st, sx, sy, ex, ey, c0, c1, NULL); + ps_clear(); + rendered = true; + } + } + else if (strcmp(tmp, "pgfRrgb") == 0 || strcmp(tmp, "pgfR1rgb") == 0) { + // Radial RGB: sx sy sr ex ey er R1 G1 B1 R2 G2 B2 (12 params) + if (ps_depth() >= 12) { + float b2=ps_pop(), g2=ps_pop(), r2=ps_pop(); + float b1=ps_pop(), g1=ps_pop(), r1=ps_pop(); + float er=ps_pop(), ey=ps_pop(), ex=ps_pop(); + float sr=ps_pop(), sy=ps_pop(), sx=ps_pop(); + float c0[3] = {r1, g1, b1}; + float c1[3] = {r2, g2, b2}; + { + shade_subfunc sf = {{c0[0],c0[1],c0[2]},{c1[0],c1[1],c1[2]}}; + render_radial_shade(ctx, dc, st, sx, sy, sr, er, &sf, 1, NULL, 0); + } + ps_clear(); + rendered = true; + } + } + // Try as a user-defined function call + else { + const char *b = ps_lookup_func(tmp); + if (b) { + ps_exec_body(ctx, dc, st, b, strlen(b), PS_COLOR_BOTH); + ps_clear(); // user functions must not leak stack values + rendered = true; // user-defined functions typically render + } + } + } + + // Drop path if a fill/stroke operation left it behind + // (pgffill keeps the path for a possible pgfstr within the same + // special; pgfstr already drops it). Without this, the next + // drawing operation inherits stale path segments. + if (rendered && dc->path) + drop_path(ctx, dc); + + ps_clear(); + return 1; +} + +// Execute a stored PS function body (simple token interpreter) +// The stack should already have the function arguments; body consumes them. +// Do NOT save/restore ps_sp — let the body naturally consume and push values. +static void +ps_exec_body(fz_context *ctx, dvi_context *dc, dvi_state *st, + const char *body, int body_len, int color_target) +{ + const char *p = body, *end = body + body_len; + bool rendered = false; + while (p < end) + { + while (p < end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) p++; + if (p >= end) break; + + const char *ts = p; + while (p < end && *p != ' ' && *p != '\n' && *p != '\r' && *p != '\t') p++; + int tl = p - ts; + if (tl == 0) continue; + + char tmp[64]; + if (tl > 63) tl = 63; + memcpy(tmp, ts, tl); tmp[tl] = 0; + + // Strip [ and ] from PS array syntax + char *num_start = tmp; + if (*num_start == '[') num_start++; + int num_len = strlen(num_start); + if (num_len > 0 && num_start[num_len-1] == ']') { + num_start[num_len-1] = 0; + num_len--; + } + char *ep; + float fv = strtof(num_start, &ep); + if (ep == num_start + num_len && num_len > 0 && *num_start != '/') { + ps_push(fv); + continue; + } + + int ct = color_target; + // PS array markers + if (tmp[0] == '[' && tmp[1] == 0) continue; + if (tmp[0] == ']' && tmp[1] == 0) continue; + // Same commands as ps_code but without function defs + if (strcmp(tmp, "setgray") == 0) { + if (ps_depth() >= 1) { float g=ps_pop(); + if (ct != PS_COLOR_STROKE) color_set_gray(st->gs.colors.fill,g); + if (ct != PS_COLOR_FILL) color_set_gray(st->gs.colors.line,g); + } + } + else if (strcmp(tmp, "setrgbcolor") == 0) { + if (ps_depth() >= 3) { float b=ps_pop(),g=ps_pop(),r=ps_pop(); + if (ct != PS_COLOR_STROKE) color_set_rgb(st->gs.colors.fill,r,g,b); + if (ct != PS_COLOR_FILL) color_set_rgb(st->gs.colors.line,r,g,b); + } + } + else if (strcmp(tmp, "setcmykcolor") == 0) { + if (ps_depth() >= 4) { float k=ps_pop(),y=ps_pop(),m=ps_pop(),c=ps_pop(); + if (ct != PS_COLOR_STROKE) color_set_cmyk(st->gs.colors.fill,c,m,y,k); + if (ct != PS_COLOR_FILL) color_set_cmyk(st->gs.colors.line,c,m,y,k); + } + } + else if (strcmp(tmp, "fillopacity") == 0) { + if (ps_depth() >= 1) st->gs.fill_alpha = ps_pop(); + } + else if (strcmp(tmp, "strokeopacity") == 0) { + if (ps_depth() >= 1) st->gs.stroke_alpha = ps_pop(); + } + else if (strcmp(tmp, "pgfw") == 0 || strcmp(tmp, "setlinewidth") == 0) { + if (ps_depth() >= 1) st->gs.line_width = ps_pop(); + } + else if (strcmp(tmp, "setlinecap") == 0) { + if (ps_depth() >= 1) st->gs.line_caps = (int)ps_pop(); + } + else if (strcmp(tmp, "setlinejoin") == 0) { + if (ps_depth() >= 1) st->gs.line_join = (int)ps_pop(); + } + else if (strcmp(tmp, "setdash") == 0) { + if (ps_depth() >= 1) { + float ph = ps_pop(); + st->gs.dash_phase = ph; + int n = ps_depth(); + st->gs.dash_len = n; + if (n > 32) n = 32; + for (int i = 0; i < n; i++) + st->gs.dash[i] = ps_stack[ps_sp - n + i]; + ps_sp -= n; // only consume the dash array values + } + } + // Path building commands (used in pgf function bodies like pgf1-pgf8) + else if (strcmp(tmp, "moveto") == 0) { + if (ps_depth() >= 2) { float y=ps_pop(), x=ps_pop(); fz_moveto(ctx, get_path(ctx,dc), x, y); } + } + else if (strcmp(tmp, "lineto") == 0) { + if (ps_depth() >= 2) { float y=ps_pop(), x=ps_pop(); fz_lineto(ctx, get_path(ctx,dc), x, y); } + } + else if (strcmp(tmp, "curveto") == 0) { + if (ps_depth() >= 6) { + float y3=ps_pop(),x3=ps_pop(), y2=ps_pop(),x2=ps_pop(), y1=ps_pop(),x1=ps_pop(); + fz_curveto(ctx, get_path(ctx,dc), x1,y1, x2,y2, x3,y3); + } + } + else if (strcmp(tmp, "closepath") == 0) { + fz_closepath(ctx, get_path(ctx,dc)); + } + else if (strcmp(tmp, "newpath") == 0) { + drop_path(ctx, dc); + } + // Graphics state (save/restore within function bodies) + else if (strcmp(tmp, "gsave") == 0 || strcmp(tmp, "save") == 0) { + if (st->gs_stack.depth < st->gs_stack.limit) { + st->gs_stack.base[st->gs_stack.depth] = st->gs; + st->gs_stack.depth += 1; + } + } + else if (strcmp(tmp, "grestore") == 0 || strcmp(tmp, "restore") == 0) { + if (st->gs_stack.depth > 0) { + int cd0 = st->gs.clip_depth; + st->gs_stack.depth -= 1; + st->gs = st->gs_stack.base[st->gs_stack.depth]; + if (dc->dev) for (int i = st->gs.clip_depth; i < cd0; ++i) fz_pop_clip(ctx, dc->dev); + } + } + // PS clip within function bodies + else if (strcmp(tmp, "clip") == 0 || strcmp(tmp, "eoclip") == 0 || + strcmp(tmp, "W") == 0 || strcmp(tmp, "W*") == 0 || + strcmp(tmp, "{clip}") == 0 || strcmp(tmp, "{eoclip}") == 0) { + int eofill = (strcmp(tmp, "W*") == 0 || strcmp(tmp, "eoclip") == 0 || + strcmp(tmp, "{eoclip}") == 0) ? 1 : 0; + if (dc->dev && dc->path) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_clip_path(ctx, dc->dev, dc->path, eofill, ctm, fz_infinite_rect); + st->gs.clip_depth += 1; + } + drop_path(ctx, dc); + } + // Stroke/fill within function bodies + else if (strcmp(tmp, "pgfstr") == 0) { + if (dc->dev) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_stroke_state sst; + get_stroke_state(ctx, st, &sst); + fz_stroke_path(ctx, dc->dev, get_path(ctx,dc), &sst, ctm, + device_cs(ctx), st->gs.colors.line, st->gs.stroke_alpha, color_params); + } + drop_path(ctx, dc); // stroke is final + rendered = true; + } + else if (strcmp(tmp, "pgffill") == 0) { + if (dc->dev) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_fill_path(ctx, dc->dev, get_path(ctx,dc), 0, ctm, + device_cs(ctx), st->gs.colors.fill, st->gs.fill_alpha, color_params); + } + rendered = true; + // Do NOT drop path — pgfstr may follow + } + // concat for CTM transforms within function bodies. + // Chain to dvi_get_ctm so the TeX page position is included, + // same rationale as the top-level concat handler in ps_code. + else if (strcmp(tmp, "concat") == 0) { + if (ps_depth() >= 6) { + float f=ps_pop(), e=ps_pop(), d=ps_pop(), c=ps_pop(), b=ps_pop(), a=ps_pop(); + fz_matrix mat; + mat.a = a; mat.b = b; mat.c = c; + mat.d = d; mat.e = e; mat.f = f; + st->gs.ctm = fz_concat(mat, dvi_get_ctm(dc, st)); + st->gs.h = st->registers.h; + st->gs.v = st->registers.v; + // 6 values already popped, stack is correct + } + } + // PGF opacity within function bodies + else if (strcmp(tmp, ".pgfsetfillopacityalpha") == 0) { + if (ps_depth() >= 1) st->gs.fill_alpha = ps_pop(); + } + else if (strcmp(tmp, ".pgfsetstrokeopacityalpha") == 0) { + if (ps_depth() >= 1) st->gs.stroke_alpha = ps_pop(); + } + // PS stack ops within function bodies + else if (strcmp(tmp, "dup") == 0) { + if (ps_depth() >= 1) { float v = ps_pop(); ps_push(v); ps_push(v); } + } + else if (strcmp(tmp, "pop") == 0) { + ps_pop(); + } + else if (strcmp(tmp, "exch") == 0) { + if (ps_depth() >= 2) { float a=ps_pop(), b=ps_pop(); ps_push(a); ps_push(b); } + } + else if (strcmp(tmp, "neg") == 0) { + if (ps_depth() >= 1) { float v = ps_pop(); ps_push(-v); } + } + // PS currentpoint / translate / scale / rotate (used by graphicx) + else if (strcmp(tmp, "currentpoint") == 0) { + fz_point pt = fz_currentpoint(ctx, get_path(ctx, dc)); + ps_push(pt.x); ps_push(pt.y); + } + else if (strcmp(tmp, "translate") == 0) { + if (ps_depth() >= 2) { + float ty=ps_pop(), tx=ps_pop(); + st->gs.ctm = fz_pre_translate(dvi_get_ctm(dc, st), tx, ty); + st->gs.h = st->registers.h; + st->gs.v = st->registers.v; + } + } + else if (strcmp(tmp, "scale") == 0) { + if (ps_depth() >= 2) { + float sy=ps_pop(), sx=ps_pop(); + st->gs.ctm = fz_pre_scale(dvi_get_ctm(dc, st), sx, sy); + st->gs.h = st->registers.h; + st->gs.v = st->registers.v; + } + } + else if (strcmp(tmp, "rotate") == 0) { + if (ps_depth() >= 1) { + float angle = ps_pop(); + st->gs.ctm = fz_pre_rotate(dvi_get_ctm(dc, st), angle); + st->gs.h = st->registers.h; + st->gs.v = st->registers.v; + } + } + else if (strcmp(tmp, "a") == 0) { + // PGF /a function: pops x,y, does moveto + if (ps_depth() >= 2) { + float y=ps_pop(), x=ps_pop(); + fz_moveto(ctx, get_path(ctx, dc), x, y); + } + } + else if (strcmp(tmp, "rlineto") == 0) { + if (ps_depth() >= 2) { + float y=ps_pop(), x=ps_pop(); + fz_path *p = get_path(ctx, dc); + fz_point cp = fz_currentpoint(ctx, p); + fz_lineto(ctx, p, cp.x + x, cp.y + y); + } + } + // PS clip operators in function bodies + else if (strcmp(tmp, "W") == 0 || strcmp(tmp, "W*") == 0) { + int eofill = (tmp[0] == 'W' && tmp[1] == '*') ? 1 : 0; + if (dc->dev) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_clip_path(ctx, dc->dev, get_path(ctx, dc), eofill, ctm, + fz_infinite_rect); + st->gs.clip_depth += 1; + } + drop_path(ctx, dc); + } + else if (strcmp(tmp, "get") == 0 || strcmp(tmp, "ifelse") == 0) { + if (ps_depth() >= 2) { ps_pop(); ps_pop(); } + } + // PGF dvips stroke/fill aliases in function bodies + else if (strcmp(tmp, "pgfs") == 0 || strcmp(tmp, "pgfS") == 0) { + if (dc->dev) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_stroke_state sst; + get_stroke_state(ctx, st, &sst); + fz_stroke_path(ctx, dc->dev, get_path(ctx,dc), &sst, ctm, + device_cs(ctx), st->gs.colors.line, st->gs.stroke_alpha, color_params); + } + drop_path(ctx, dc); + rendered = true; + } + else if (strcmp(tmp, "pgfr") == 0 || strcmp(tmp, "pgfR") == 0) { + if (dc->dev) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_fill_path(ctx, dc->dev, get_path(ctx,dc), 0, ctm, + device_cs(ctx), st->gs.colors.fill, st->gs.fill_alpha, color_params); + } + rendered = true; + // Do NOT drop path — pgfstr may follow within the same body + } + // Raw PS stroke/fill (used in PaintProc bodies) + else if (strcmp(tmp, "stroke") == 0) { + if (dc->dev && dc->path) { + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_stroke_state sst; + get_stroke_state(ctx, st, &sst); + fz_stroke_path(ctx, dc->dev, dc->path, &sst, ctm, + device_cs(ctx), st->gs.colors.line, st->gs.stroke_alpha, color_params); + } + drop_path(ctx, dc); + rendered = true; + } + else if (strcmp(tmp, "fill") == 0 || strcmp(tmp, "eofill") == 0) { + if (dc->dev && dc->path) { + int eofill = (tmp[0] == 'e') ? 1 : 0; + fz_matrix ctm = dvi_get_ctm(dc, st); + fz_fill_path(ctx, dc->dev, dc->path, eofill, ctm, + device_cs(ctx), st->gs.colors.fill, st->gs.fill_alpha, color_params); + } + drop_path(ctx, dc); + rendered = true; + } + // Pattern/dict-related no-ops (appear in pattern body wrappers) + else if (strcmp(tmp, "begin") == 0 || strcmp(tmp, "end") == 0 || + strcmp(tmp, "dict") == 0 || strcmp(tmp, "bind") == 0 || + strcmp(tmp, "def") == 0 || strcmp(tmp, "initgraphics") == 0 || + strcmp(tmp, "setcolorspace") == 0 || strcmp(tmp, "setcolor") == 0 || + strcmp(tmp, "makepattern") == 0 || strcmp(tmp, "setpattern") == 0 || + strcmp(tmp, "matrix") == 0) { + // no-op: these are pattern machinery commands we don't need + } + // ignore other commands in body + } + + // Drop path if a fill left it behind (same rationale as ps_code) + if (rendered && dc->path) + drop_path(ctx, dc); +} + +// Parse a pgf PostScript shading special and render natively. +// Handles common shading types: axial (pgfHrgb/Vrgb/Argb) and radial (pgfRrgb). +// Returns 1 if handled, 0 if not. +static bool +dvi_exec_pgf_shading(fz_context *ctx, dvi_context *dc, dvi_state *st, + cursor_t cur, cursor_t lim) +{ + // Scan for known shading function invocations. + // Skip past function definitions (between { and } bind def). + // Look for: param1 param2 ... paramN funcName + + // Known shading functions and their parameter counts + // Axial: 11 params (startx starty endx endy R1 G1 B1 R2 G2 B2 depth) + funcName + // Radial: more complex + + // Simple approach: scan for function names and parse backward for params + static const char *func_names[] = { + "pgfHrgb", "pgfVrgb", "pgfArgb", // axial RGB + "pgfHcmyk", "pgfVcmyk", "pgfAcmyk", // axial CMYK + "pgfRrgb", "pgfR1rgb", // radial RGB + NULL + }; + + for (int fi = 0; func_names[fi]; fi++) + { + const char *fname = func_names[fi]; + int flen = strlen(fname); + + // Search for function name in the special content + for (cursor_t p = cur; p + flen <= lim; p++) + { + if (memcmp(p, fname, flen) == 0) + { + // Check that it's a standalone token (preceded by whitespace/newline) + if (p > cur && !(*(p-1) == ' ' || *(p-1) == '\n' || *(p-1) == '\r')) + continue; + // Check end of token + if (p + flen < lim && !(*(p+flen) == ' ' || *(p+flen) == '\n' || *(p+flen) == '\r')) + continue; + + // Parse backwards to get parameters + // Skip the function definition if this is a 'def' not an invocation + cursor_t after = p + flen; + // Skip trailing whitespace + while (after < lim && (*after == ' ' || *after == '\n' || *after == '\r')) + after++; + + // This is an invocation if not followed by '{' or 'bind def' + // For now, try to parse parameters before the function name + float params[20] = {0}; + int nparams = 0; + cursor_t q = p; + + // Scan backwards to find start of numeric parameters + while (q > cur && nparams < 15) + { + // Skip whitespace backwards + while (q > cur && (*(q-1) == ' ' || *(q-1) == '\n' || *(q-1) == '\r')) + q--; + + // Find start of this token + cursor_t tok_start = q; + while (tok_start > cur && *(tok_start-1) != ' ' && *(tok_start-1) != '\n' + && *(tok_start-1) != '\r' && *(tok_start-1) != '{' && *(tok_start-1) != '}') + tok_start--; + + // Try to parse as float + char tmp[64]; + int tlen = q - tok_start; + if (tlen > 63) break; + memcpy(tmp, tok_start, tlen); + tmp[tlen] = 0; + + // Check if this is a number + bool is_num = true; + int dots = 0; + for (int k = 0; k < tlen; k++) + { + if (tmp[k] == '-') { if (k > 0) { is_num = false; break; } } + else if (tmp[k] == '.') { dots++; if (dots > 1) { is_num = false; break; } } + else if (tmp[k] < '0' || tmp[k] > '9') { is_num = false; break; } + } + if (!is_num || tlen == 0) break; + + params[nparams++] = pfloat(tok_start, q); + q = tok_start; + } + + if (nparams < 2) continue; + + // Reverse params (we parsed them backwards) + for (int i = 0; i < nparams / 2; i++) + { + float tmp2 = params[i]; + params[i] = params[nparams - 1 - i]; + params[nparams - 1 - i] = tmp2; + } + + // Check if this is an axial shading + bool is_axial = (fi <= 2); // pgfHrgb, pgfVrgb, pgfArgb + bool is_cmyk = (fi >= 3 && fi <= 5); + bool is_radial = (fi >= 6); + + if (is_axial && nparams >= 11) + { + float sx = params[0], sy = params[1]; + float ex = params[2], ey = params[3]; + float c0[3], c1[3]; + + if (is_cmyk) + { + color_set_cmyk(c0, params[4], params[5], params[6], params[7]); + color_set_cmyk(c1, params[8], params[9], params[10], params[11]); + } + else + { + color_set_rgb(c0, params[4], params[5], params[6]); + color_set_rgb(c1, params[8], params[9], params[10]); + } + + render_axial_shade(ctx, dc, st, sx, sy, ex, ey, c0, c1, NULL); + return 1; + } + + if (is_radial && nparams >= 10) + { + // Radial: startx starty startr endx endy endr R1 G1 B1 R2 G2 B2 + float sx = params[0], sy = params[1]; + float ex = params[2], ey = params[3]; + float c0[3] = {params[4], params[5], params[6]}; + float c1[3] = {params[7], params[8], params[9]}; + float r0 = 0, r1 = sqrtf((ex - sx) * (ex - sx) + (ey - sy) * (ey - sy)); + + { + shade_subfunc sf = {{c0[0],c0[1],c0[2]},{c1[0],c1[1],c1[2]}}; + render_radial_shade(ctx, dc, st, sx, sy, r0, r1, &sf, 1, NULL, 0); + } + return 1; + } + + // If we found a function name but couldn't parse params, still count as handled + return 1; + } + } + } + + // Not a recognized shading pattern + return 1; // Still return 1 to avoid aborting the page +} + static bool dvi_exec_pdf(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, cursor_t lim) { @@ -1175,6 +3363,10 @@ dvi_exec_pdf(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, curs @f0 ("bcontent" | "econtent") @f1 { + // bcontent/econtent: these delimit content streams but the tikz + // engine also emits explicit q/Q and btrans/etrans for state + // management. Treating these as gsave/grestore would double-push + // and cause the text to lose the btrans CTM. if (f1 != lim) fprintf(stderr, "unhandled pdf content: %.*s\n", (int)(lim - f0), f0); @@ -1187,11 +3379,20 @@ dvi_exec_pdf(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, curs if (!colorstack_push(ctx, dc, st, -1)) return 0; if (f3) + { color_set_cmyk(st->gs.colors.fill, pfloat(f0, lim), pfloat(f1, lim), pfloat(f2, lim), pfloat(f3, lim)); + color_set_cmyk(st->gs.colors.line, pfloat(f0, lim), pfloat(f1, lim), pfloat(f2, lim), pfloat(f3, lim)); + } else if (f1) + { color_set_rgb(st->gs.colors.fill, pfloat(f0, lim), pfloat(f1, lim), pfloat(f2, lim)); + color_set_rgb(st->gs.colors.line, pfloat(f0, lim), pfloat(f1, lim), pfloat(f2, lim)); + } else + { color_set_gray(st->gs.colors.fill, pfloat(f4 ? f4 : f0, lim)); + color_set_gray(st->gs.colors.line, pfloat(f4 ? f4 : f0, lim)); + } return 1; } @@ -1200,9 +3401,142 @@ dvi_exec_pdf(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t cur, curs return colorstack_pop(ctx, dc, st, -1); } + "q" + { + if (st->gs_stack.depth >= st->gs_stack.limit) return 0; + st->gs_stack.base[st->gs_stack.depth] = st->gs; + st->gs_stack.depth += 1; + return 1; + } + + "Q" + { + if (st->gs_stack.depth == 0) return 0; + int clip_depth0 = st->gs.clip_depth; + st->gs_stack.depth -= 1; + st->gs = st->gs_stack.base[st->gs_stack.depth]; + if (dc->dev) + for (int i = st->gs.clip_depth; i < clip_depth0; ++i) + fz_pop_clip(ctx, dc->dev); + return 1; + } + + "cm" ws+ @f0 float ws+ @f1 float ws+ @f2 float ws+ + @f3 float ws+ @f4 float ws+ @f5 float + { + // Standalone cm operator (used by PGF for text positioning in nodes) + fz_matrix mat; + mat.a = pfloat(f0, lim); mat.b = pfloat(f1, lim); + mat.c = pfloat(f2, lim); mat.d = pfloat(f3, lim); + mat.e = pfloat(f4, lim); mat.f = pfloat(f5, lim); + fz_matrix ctm = fz_concat(mat, dvi_get_ctm(dc, st)); + dvi_set_ctm(st, ctm); + return 1; + } + "code" { return pdf_code(ctx, dc, st, cur, lim); } + "literal" ws+ ("direct" ws+)? + { return pdf_code(ctx, dc, st, cur, lim); } + + "stream" ws+ + { + // Parse: stream @name (content) << dict >> + // Skip whitespace + while (cur < lim && (*cur == ' ' || *cur == '\t')) cur++; + // Read object name + const char *obj_start = cur; + while (cur < lim && *cur != ' ' && *cur != '\t' && *cur != '(') cur++; + int obj_len = (int)(cur - obj_start); + while (cur < lim && (*cur == ' ' || *cur == '\t')) cur++; + // Read content in parentheses: (...) + const char *content = NULL; + int content_len = 0; + if (cur < lim && *cur == '(') { + cur++; // skip ( + content = cur; + int depth = 1; + while (cur < lim && depth > 0) { + if (*cur == '(' && (cur == content || *(cur-1) != '\\')) depth++; + else if (*cur == ')' && (cur == content || *(cur-1) != '\\')) depth--; + if (depth > 0) cur++; + } + content_len = (int)(cur - content); + if (cur < lim) cur++; // skip closing ) + } + while (cur < lim && (*cur == ' ' || *cur == '\t')) cur++; + // Read dictionary in << ... >> + const char *dict = NULL; + int dict_len = 0; + if (cur + 1 < lim && cur[0] == '<' && cur[1] == '<') { + cur += 2; // skip << + dict = cur; + int dd = 1; + while (cur + 1 < lim && dd > 0) { + if (cur[0] == '<' && cur[1] == '<') { dd++; cur += 2; } + else if (cur[0] == '>' && cur[1] == '>') { dd--; if (dd > 0) cur += 2; } + else cur++; + } + dict_len = (int)(cur - dict); + } + // Store pattern definition if it's a pattern object + if (content && obj_len > 0) { + pdf_pat_define(obj_start, obj_len, content, content_len, + dict ? dict : "", dict_len); + } + return 1; + } + + "obj" ws+ + { + // pdf:obj @name << dict >> — create named dictionary object (no-op for us) + return 1; + } + + "put" ws+ + { + // Parse: put @objname << /key1 @val1 /key2 @val2 ... >> + // We specifically look for: put @pgfpatterns << /pgfpatN @pgfpatternobjectN >> + while (cur < lim && (*cur == ' ' || *cur == '\t')) cur++; + const char *obj_start = cur; + while (cur < lim && *cur != ' ' && *cur != '\t' && *cur != '<') cur++; + int obj_len = (int)(cur - obj_start); + // Check if this is @pgfpatterns + if (obj_len >= 13 && memcmp(obj_start[0] == '@' ? obj_start+1 : obj_start, + "pgfpatterns", 11) == 0) { + while (cur < lim && (*cur == ' ' || *cur == '\t')) cur++; + if (cur + 1 < lim && cur[0] == '<' && cur[1] == '<') { + cur += 2; // skip << + // Parse key-value pairs: /pgfpatN @pgfpatternobjectN + while (cur < lim) { + while (cur < lim && (*cur == ' ' || *cur == '\t' || *cur == '\n')) cur++; + if (cur + 1 < lim && cur[0] == '>' && cur[1] == '>') break; + if (cur < lim && *cur == '/') { + cur++; // skip / + const char *pn = cur; + while (cur < lim && *cur != ' ' && *cur != '\t' && *cur != '>' && *cur != '/') cur++; + int pnl = (int)(cur - pn); + while (cur < lim && (*cur == ' ' || *cur == '\t')) cur++; + const char *on = cur; + while (cur < lim && *cur != ' ' && *cur != '\t' && *cur != '>' && *cur != '/') cur++; + int onl = (int)(cur - on); + if (pnl > 0 && onl > 0) { + pdf_pat_map_name(pn, pnl, on, onl); + } + } else { + cur++; + } + } + } + } + return 1; + } + + "bxobj" ws+ { return 1; } + "exobj" [^\x00]* { return 1; } + "uxobj" ws+ { return 1; } + '' { return unhandled("pdf special", cur, lim, 0); } @@ -1213,8 +3547,6 @@ bool dvi_exec_special(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t { cursor_t mar, i, j; - // fprintf(stderr, "special: %.*s\n", (int)(lim - cur), cur); - for (;;) { /*!re2c @@ -1269,6 +3601,49 @@ bool dvi_exec_special(fz_context *ctx, dvi_context *dc, dvi_state *st, cursor_t "pdf:" ws* { return dvi_exec_pdf(ctx, dc, st, cur, lim); } + "papersize=" @i dim "," @j dim + { + dc->page_width = pdim(i, lim); + dc->page_height = pdim(j, lim); + return 1; + } + + "!" ws* "/pgf" + { + // PGF "!" specials: always parse function definitions. + // IMPORTANT: the match consumed "/pgf" from the content (e.g. from + // "/pgfsc"), so back up 4 chars so ps_parse_defs sees the full + // function name including the /pgf prefix. + cursor_t content_start = cur - 4; // include "/pgf" that was consumed + ps_parse_defs(content_start, lim); + if (dc->dev) + return dvi_exec_pgf_shading(ctx, dc, st, cur, lim); + return 1; + } + + "!" ws* + { + // General "!" specials: scan for any function definitions. + // Pattern declarations come as: save true setglobal globaldict begin + // /pgfpatN { ... } bind def end restore + // These don't start with /pgf so the specific handler above misses them. + // Scan for /pgfpat in the content to route to pattern table. + ps_parse_defs(cur, lim); + return 1; + } + + "ps:" ws* + { + // ps: (single colon) — state management: pgfsc/pgffc clear, etc. + // Process same as ps:: so that pgfsc{}/pgffc{} clears take effect. + return ps_code(ctx, dc, st, cur, lim); + } + + "ps::" ws* + { + return ps_code(ctx, dc, st, cur, lim); + } + '' { return unhandled("special", cur, lim, 0); } diff --git a/src/dvi/mydvi.h b/src/dvi/mydvi.h index e2f21059..1e9784a1 100644 --- a/src/dvi/mydvi.h +++ b/src/dvi/mydvi.h @@ -273,6 +273,32 @@ typedef enum PDF_SQUARE_CAPS = 2, } pdf_line_caps; +// Text state for PDF text operators (BT/ET/Tf/Tj/TJ etc.) +// Used by TikZ specials to render node labels and decorations. +typedef struct +{ + fz_matrix Tm; // text matrix + fz_matrix Tlm; // text line matrix + float char_space; // Tc + float word_space; // Tw + float scale; // Tz (horizontal scaling, default 100 = 1.0) + float leading; // TL + int render; // Tr (0=fill, 1=stroke, 2=fill+stroke, 3=invisible) + float rise; // Ts + char font_name[64]; // font name from Tf + float font_size; // font size from Tf + int in_text; // true between BT and ET +} dvi_textstate; + +// Simple font cache for special (TikZ) text rendering. +// Standard PDF 14 fonts are resolved lazily and cached here. +#define DVI_FONT_CACHE_SIZE 8 +typedef struct +{ + char name[64]; + fz_font *font; +} dvi_font_cache_entry; + typedef struct { fz_matrix ctm; @@ -281,10 +307,13 @@ typedef struct pdf_line_join line_join; pdf_line_caps line_caps; int clip_depth; - float dash[4]; + float fill_alpha; + float stroke_alpha; + float dash[32]; int dash_len; float dash_phase; int h, v; + dvi_textstate text; } dvi_graphicstate; typedef struct @@ -336,6 +365,15 @@ typedef struct // Pdf color stacks (introduced by pdftex) dvi_colorstacks pdfcolorstacks; float scale; + + // Font cache for special (TikZ) text operators + dvi_font_cache_entry font_cache[DVI_FONT_CACHE_SIZE]; + int font_cache_count; + + // Saved frame base CTM (for PS concat to build from) + fz_matrix base_ctm; + // Page dimensions in big points (for PS coordinate conversion) + float page_width, page_height; } dvi_context; #define DC_ALLOC(ctx, dc, type, count) ((type*)dvi_scratch_alloc(ctx, &(dc)->scratch, sizeof(type) * (count))) @@ -350,6 +388,9 @@ void dvi_context_end_frame(fz_context *ctx, dvi_context *dc); #define inlined static inline __attribute__((unused)) +// PS state reset (called at begin_frame to prevent cross-page contamination) +void ps_state_reset(void); + inlined fz_matrix dvi_get_ctm(const dvi_context *dc, const dvi_state *st) { float s = dc->scale; diff --git a/src/engine/main/zlib_md5.c b/src/engine/main/zlib_md5.c index 0ce5f4a1..6f1c8497 100644 --- a/src/engine/main/zlib_md5.c +++ b/src/engine/main/zlib_md5.c @@ -67,7 +67,7 @@ int ttbc_get_data_md5(const uint8_t *data, _picohash_md5_init(&ctx); _picohash_md5_update(&ctx, data, len); _picohash_md5_final(&ctx, digest); - return 1; + return 0; } int ttstub_get_file_md5(const char *path, @@ -76,7 +76,7 @@ int ttstub_get_file_md5(const char *path, ttbc_input_handle_t *input = ttstub_input_open(path, TTBC_FILE_FORMAT_PROGRAM_DATA, 0); if (!input) - return 0; + return 1; _picohash_md5_ctx_t ctx; _picohash_md5_init(&ctx); @@ -90,5 +90,5 @@ int ttstub_get_file_md5(const char *path, _picohash_md5_final(&ctx, digest); - return 1; + return 0; } diff --git a/src/frontend/driver.c b/src/frontend/driver.c index 50add91d..312792e3 100644 --- a/src/frontend/driver.c +++ b/src/frontend/driver.c @@ -254,8 +254,11 @@ int main(int argc, const char **argv) if (stream_mode) { // In stream mode, the file may not exist on disk yet. - // Construct doc_path from cwd + doc_arg basename. - snprintf(doc_path, PATH_MAX, "%s/%s", work_dir, doc_arg); + // Absolute paths used as-is; relative paths resolved against cwd. + if (doc_arg[0] == '/') + snprintf(doc_path, PATH_MAX, "%s", doc_arg); + else + snprintf(doc_path, PATH_MAX, "%s/%s", work_dir, doc_arg); } else { @@ -339,7 +342,9 @@ int main(int argc, const char **argv) .use_tectonic = use_tectonic, .use_texlive = use_texlive, .initialize_only = initialize_only, - .stream_mode = stream_mode + .stream_mode = stream_mode, + // In stream mode, start paused: editor primes the VFS before (resume). + .paused = stream_mode, }; int exit_code = 0; diff --git a/src/frontend/driver.h b/src/frontend/driver.h index 229c7454..4f35d714 100644 --- a/src/frontend/driver.h +++ b/src/frontend/driver.h @@ -70,6 +70,7 @@ struct persistent_state { const char *exe_path, *doc_path, *doc_name, *inclusion_path; bool line_output, use_tectonic, use_texlive, initialize_only, stream_mode; + bool paused; }; bool texpresso_main(struct persistent_state *ps); diff --git a/src/frontend/editor.c b/src/frontend/editor.c index dac5b1dd..6ceddc9f 100644 --- a/src/frontend/editor.c +++ b/src/frontend/editor.c @@ -281,6 +281,30 @@ bool editor_parse(fz_context *ctx, goto arity; *out = (struct editor_command){.tag = EDIT_INVERT, .invert = {}}; } + else if (strcmp(verb, "register") == 0) + { + if (len != 2) + goto arity; + val path = val_array_get(ctx, stack, command, 1); + if (!val_is_string(path)) + goto arguments; + *out = (struct editor_command){ + .tag = EDIT_REGISTER, + .reg = { .path = val_string(ctx, stack, path) }, + }; + } + else if (strcmp(verb, "pause") == 0) + { + if (len != 1) + goto arity; + *out = (struct editor_command){.tag = EDIT_PAUSE, .pause = {}}; + } + else if (strcmp(verb, "resume") == 0) + { + if (len != 1) + goto arity; + *out = (struct editor_command){.tag = EDIT_RESUME, .resume = {}}; + } else { fprintf(stderr, "[command] unknown verb: %s\n", verb); @@ -586,3 +610,44 @@ void editor_notify_file_opened(int index, const char *path, int len) case EDITOR_JSON: fprintf(stdout, "\"]\n"); break; } } + +void editor_notify_lookup(const char *path, + int len, + bool read, + enum EDITOR_LOOKUP_STATUS status) +{ + const char *kind = read ? "read" : "write"; + const char *status_msg; + + switch (status) + { + case LOOKUP_FAILED: + status_msg = "failed"; + break; + case LOOKUP_PROMISED: + status_msg = "promised"; + break; + case LOOKUP_SUCCESSFUL: + status_msg = "successful"; + break; + default: + abort(); + } + + switch (protocol) + { + case EDITOR_SEXP: + fprintf(stdout, "(lookup-file %s %s \"", kind, status_msg); + break; + case EDITOR_JSON: + fprintf(stdout, "[\"lookup-file\", \"%s\", \"%s\", \"", kind, status_msg); + break; + } + + output_data_string(stdout, path, len); + switch (protocol) + { + case EDITOR_SEXP: fprintf(stdout, "\")\n"); break; + case EDITOR_JSON: fprintf(stdout, "\"]\n"); break; + } +} diff --git a/src/frontend/editor.h b/src/frontend/editor.h index 6aef93f8..d0cae343 100644 --- a/src/frontend/editor.h +++ b/src/frontend/editor.h @@ -32,6 +32,9 @@ enum EDITOR_COMMAND EDIT_UNMAP_WINDOW, EDIT_CROP, EDIT_INVERT, + EDIT_REGISTER, + EDIT_PAUSE, + EDIT_RESUME, }; struct editor_change @@ -114,6 +117,16 @@ struct editor_command struct { } invert; + + struct { + const char *path; + } reg; + + struct { + } pause; + + struct { + } resume; }; }; @@ -137,4 +150,16 @@ void editor_synctex(const char *dirname, const char *basename, int basename_len, void editor_reset_sync(void); void editor_notify_file_opened(int index, const char *path, int len); +enum EDITOR_LOOKUP_STATUS +{ + LOOKUP_SUCCESSFUL, + LOOKUP_FAILED, + LOOKUP_PROMISED, +}; + +void editor_notify_lookup(const char *path, + int len, + bool read, + enum EDITOR_LOOKUP_STATUS status); + #endif // EDITOR_H_ diff --git a/src/frontend/engine_tex.c b/src/frontend/engine_tex.c index 152f8676..a36ecee1 100644 --- a/src/frontend/engine_tex.c +++ b/src/frontend/engine_tex.c @@ -58,6 +58,11 @@ typedef struct mark_t snap; } process_t; +enum +{ + MAX_PROCESS = 32 +}; + struct tex_engine { struct txp_engine_class *_class; @@ -73,7 +78,7 @@ struct tex_engine log_t *log; channel_t *c; - process_t processes[32]; + process_t processes[MAX_PROCESS]; int process_count; trace_entry_t *trace; @@ -88,6 +93,12 @@ struct tex_engine struct { int trace_len, offset, flush; } rollback; + + struct { + bool active; + query_t query; + char path[1024]; + } deferred; }; // Backtrackable process state & VFS representation @@ -191,6 +202,7 @@ static void prepare_process(fz_context *ctx, struct tex_engine *self) { if (self->process_count == 0) { + self->deferred.active = false; log_rollback(ctx, self->log, self->restart); self->process_count = 1; process_t *p = get_process(self); @@ -238,6 +250,9 @@ static bool read_query(struct tex_engine *self, channel_t *t, query_t *q) static void decimate_processes(struct tex_engine *self) { + bool keep[MAX_PROCESS] = {0,}; + + int target = 32; fprintf(stderr, "before process decimation:\n"); for (int i = 0; i < self->process_count; ++i) { @@ -246,18 +261,41 @@ static void decimate_processes(struct tex_engine *self) p->trace_len, p->trace_len == 0 ? 0 : self->trace[p->trace_len - 1].time, p->pid); + if (p->trace_len >= target) + { + keep[i] = true; + target *= 2; + } } - int i = 0, bound = (self->process_count - 8) / 2; - while (i < bound) + target = self->processes[self->process_count - 1].trace_len; + int delta = 32; + for (int i = self->process_count - 1; i >= 0; --i) { - close_process(&self->processes[2*i]); - self->processes[i] = self->processes[2*i+1]; - i++; + process_t *p = &self->processes[i]; + if (p->trace_len <= target) + { + keep[i] = true; + delta *= 2; + target -= delta; + } + else if (keep[i]) + { + delta *= 2; + target = p->trace_len - delta; + } } - for (int j = bound * 2; j < self->process_count; ++j) + + int i = 0; + for (int j = 0; j < self->process_count; j++) { - self->processes[i] = self->processes[j]; + if (!keep[j]) + { + close_process(&self->processes[j]); + continue; + } + if (i != j) + self->processes[i] = self->processes[j]; i++; } self->process_count = i; @@ -493,10 +531,30 @@ static void answer_query(fz_context *ctx, struct tex_engine *self, query_t *q) if (!fs_path) { e = filesystem_lookup_or_create(ctx, self->fs, q->open.path); - log_fileentry(ctx, self->log, e); - record_seen(self, e, INT_MAX, q->time); - a.tag = A_PASS; - channel_write_answer(self->c, p->fd, &a); + if (e->promised && !self->deferred.active) + { + // File was promised and is missing: notify the editor and wait + // for answer + self->deferred.active = true; + self->deferred.query = *q; + strncpy(self->deferred.path, q->open.path, + sizeof(self->deferred.path) - 1); + self->deferred.path[sizeof(self->deferred.path) - 1] = '\0'; + self->deferred.query.open.path = self->deferred.path; + editor_notify_lookup(q->open.path, strlen(q->open.path), true, + LOOKUP_PROMISED); + } + else + { + // File is missing: record this observation and mark the lookup + // as failed. + log_fileentry(ctx, self->log, e); + record_seen(self, e, INT_MAX, q->time); + a.tag = A_PASS; + editor_notify_lookup(q->open.path, strlen(q->open.path), true, + LOOKUP_FAILED); + channel_write_answer(self->c, p->fd, &a); + } break; } } @@ -529,6 +587,8 @@ static void answer_query(fz_context *ctx, struct tex_engine *self, query_t *q) log_fileentry(ctx, self->log, e); record_seen(self, e, INT_MAX, q->time); a.tag = A_PASS; + editor_notify_lookup(q->open.path, strlen(q->open.path), + q->tag == Q_OPRD, LOOKUP_FAILED); channel_write_answer(self->c, p->fd, &a); break; } @@ -620,6 +680,7 @@ static void answer_query(fz_context *ctx, struct tex_engine *self, query_t *q) int n = strlen(q->open.path); a.open.path_len = n; a.tag = A_OPEN; + editor_notify_lookup(q->open.path, n, q->tag == Q_OPRD, LOOKUP_SUCCESSFUL); memmove(channel_get_buffer(self->c, n), q->open.path, n); channel_write_answer(self->c, p->fd, &a); break; @@ -869,7 +930,7 @@ static void answer_query(fz_context *ctx, struct tex_engine *self, query_t *q) case Q_CHLD: { - if (self->process_count == 32) + if (self->process_count == MAX_PROCESS) { decimate_processes(self); p = get_process(self); @@ -903,6 +964,7 @@ static void revert_trace(trace_entry_t *te) static void rollback_processes(fz_context *ctx, struct tex_engine *self, int reverted, int trace) { + self->deferred.active = false; fprintf( stderr, "rolling back to position %d\nbefore rollback: %d bytes of output\n", @@ -1064,6 +1126,20 @@ static bool engine_step(txp_engine *_self, fz_context *ctx, bool restart_if_need if (restart_if_needed) prepare_process(ctx, self); + if (self->deferred.active) + { + fileentry_t *e = filesystem_lookup(self->fs, self->deferred.path); + if (e && e->edit_data) + { + e->seen = -1; + self->deferred.active = false; + answer_query(ctx, self, &self->deferred.query); + channel_flush(self->c, get_process(self)->fd); + return 1; + } + return 0; + } + if (engine_get_status(_self) == DOC_RUNNING) { query_t q; @@ -1159,6 +1235,10 @@ static void rollback_begin(fz_context *ctx, struct tex_engine *self) if (self->rollback.trace_len != NOT_IN_TRANSACTION) abort(); + // Skip if no worker yet (-stream paused at startup) + if (self->process_count == 0) + return; + self->rollback.trace_len = get_process(self)->trace_len; self->rollback.offset = -1; self->rollback.flush = 0; @@ -1169,9 +1249,10 @@ static bool rollback_end(fz_context *ctx, struct tex_engine *self, int *tracep, int trace_len = self->rollback.trace_len; self->rollback.trace_len = NOT_IN_TRANSACTION; - // Assert we are in a transaction + // No transaction opened: legitimate when begin was a no-op (no worker + // at begin, possibly spawned mid-iteration by an EDIT_RESUME bootstrap). if (trace_len == NOT_IN_TRANSACTION) - abort(); + return false; process_t *p = get_process(self); @@ -1188,8 +1269,11 @@ static bool rollback_end(fz_context *ctx, struct tex_engine *self, int *tracep, channel_flush(self->c, p->fd); return false; } - trace_len -= 1; - revert_trace(&self->trace[trace_len]); + if (trace_len > 0) + { + trace_len -= 1; + revert_trace(&self->trace[trace_len]); + } if (trace_len > 0) self->rollback.offset = self->trace[trace_len].seen; } @@ -1264,9 +1348,9 @@ static void rollback_add_change(fz_context *ctx, struct tex_engine *self, fileen int trace_len = self->rollback.trace_len; // if (changed > 0) changed--; - // Assert we are in a transaction + // No transaction opened: legitimate when begin was a no-op (no worker). if (trace_len == NOT_IN_TRANSACTION) - mabort(); + return; if (e->seen < changed && trace_len == get_process(self)->trace_len) { @@ -1401,6 +1485,7 @@ txp_engine *txp_create_tex_engine(fz_context *ctx, self->stex = synctex_new(ctx); self->rollback.trace_len = NOT_IN_TRANSACTION; + self->deferred.active = false; return (txp_engine*)self; } diff --git a/src/frontend/main.c b/src/frontend/main.c index cbe436aa..60fec4d0 100644 --- a/src/frontend/main.c +++ b/src/frontend/main.c @@ -788,12 +788,15 @@ static void interpret_open(struct persistent_state *ps, const void *data, int size) { - int go_up = 0; - path = relative_path(path, ps->doc_path, &go_up); - if (go_up > 0) + if (path[0] == '/') { - fprintf(stderr, "[command] open %s: file has a different root, skipping\n", path); - return; + int go_up = 0; + path = relative_path(path, ps->doc_path, &go_up); + if (go_up > 0) + { + fprintf(stderr, "[command] open %s: file has a different root, skipping\n", path); + return; + } } fileentry_t *e = send(find_file, ui->eng, ps->ctx, path); @@ -806,6 +809,7 @@ static void interpret_open(struct persistent_state *ps, flush_changes(ps, ui); int changed = -1; + bool had_edit_data = (e->edit_data != NULL); if (e->edit_data) { @@ -828,8 +832,13 @@ static void interpret_open(struct persistent_state *ps, if (changed >= 0) { - fprintf(stderr, "[command] open %s: changed offset is %d\n", path, changed); - send(notify_file_changes, ui->eng, ps->ctx, e, changed); + if (e->promised && !had_edit_data) + fprintf(stderr, "[command] open %s: resolving deferred query\n", path); + else + { + fprintf(stderr, "[command] open %s: changed offset is %d\n", path, changed); + send(notify_file_changes, ui->eng, ps->ctx, e, changed); + } } } @@ -904,6 +913,32 @@ SDL_SetWindowAlwaysOnTop(SDL_Window *window, SDL_bool state) #endif +static void interpret_register(struct persistent_state *ps, + ui_state *ui, + const char *path) +{ + if (path[0] == '/') + { + int go_up = 0; + path = relative_path(path, ps->doc_path, &go_up); + if (go_up > 0) + { + fprintf(stderr, "[command] register %s: file has a different root, skipping\n", path); + return; + } + } + + fileentry_t *e = send(find_file, ui->eng, ps->ctx, path); + if (!e) + { + fprintf(stderr, "[command] register %s: file not found, skipping\n", path); + return; + } + + e->promised = true; + fprintf(stderr, "[command] register %s: marked as promised\n", path); +} + static void interpret_command(struct persistent_state *ps, ui_state *ui, vstack *stack, @@ -1045,6 +1080,23 @@ static void interpret_command(struct persistent_state *ps, schedule_event(RENDER_EVENT); } break; + + case EDIT_REGISTER: + interpret_register(ps, ui, cmd.reg.path); + break; + + case EDIT_PAUSE: + ps->paused = true; + fprintf(stderr, "[command] pause: engine stepping suspended\n"); + break; + + case EDIT_RESUME: + ps->paused = false; + fprintf(stderr, "[command] resume: engine stepping enabled\n"); + // Spawn the worker if -stream started paused (no-op otherwise) + send(step, ui->eng, ps->ctx, true); + schedule_event(SCAN_EVENT); + break; } } @@ -1145,7 +1197,8 @@ bool texpresso_main(struct persistent_state *ps) ui->last_click_ticks = SDL_GetTicks() - 200000000; bool quit = 0, reload = 0; - send(step, ui->eng, ps->ctx, true); + if (!ps->paused) + send(step, ui->eng, ps->ctx, true); render(ps->ctx, ui); schedule_event(RELOAD_EVENT); @@ -1215,14 +1268,15 @@ bool texpresso_main(struct persistent_state *ps) if (send(end_changes, ui->eng, ps->ctx)) { - send(step, ui->eng, ps->ctx, true); + if (!ps->paused) + send(step, ui->eng, ps->ctx, true); schedule_event(RELOAD_EVENT); } // Process document { int before_page_count = send(page_count, ui->eng); - bool advance = advance_engine(ps->ctx, ui); + bool advance = !ps->paused && advance_engine(ps->ctx, ui); int after_page_count = send(page_count, ui->eng); fflush(stdout); @@ -1429,7 +1483,8 @@ bool texpresso_main(struct persistent_state *ps) send(detect_changes, ui->eng, ps->ctx); if (send(end_changes, ui->eng, ps->ctx)) { - send(step, ui->eng, ps->ctx, true); + if (!ps->paused) + send(step, ui->eng, ps->ctx, true); schedule_event(RELOAD_EVENT); } break; @@ -1440,7 +1495,8 @@ bool texpresso_main(struct persistent_state *ps) flush_changes(ps, ui); if (send(end_changes, ui->eng, ps->ctx)) { - send(step, ui->eng, ps->ctx, true); + if (!ps->paused) + send(step, ui->eng, ps->ctx, true); schedule_event(RELOAD_EVENT); } break; @@ -1461,7 +1517,9 @@ bool texpresso_main(struct persistent_state *ps) break; } } - if (ps->initialize_only) + if (ps->initialize_only && + (send(page_count, ui->eng) > 0 || + (send(get_status, ui->eng) == DOC_TERMINATED && stdin_eof))) { fprintf(stderr, "[info] Initialize mode: terminating engine process\n"); quit = 1; diff --git a/src/frontend/state.h b/src/frontend/state.h index 2613ae34..afbebcdd 100644 --- a/src/frontend/state.h +++ b/src/frontend/state.h @@ -51,6 +51,7 @@ typedef struct fileentry_s { // State of the file in the text editor (or NULL if unedited) fz_buffer *edit_data; + bool promised; // State observed and/or produced by TeX process struct { diff --git a/test/minimal-text-test.tex b/test/minimal-text-test.tex new file mode 100644 index 00000000..74875b06 --- /dev/null +++ b/test/minimal-text-test.tex @@ -0,0 +1,15 @@ +\documentclass{article} +\begin{document} +\section{Test Section} +Hello world. This is a simple test document. + +The quick brown fox jumps over the lazy dog. + +Line 1: Testing if text wraps properly. This is a very long line that should wrap to the next line if everything is working correctly. + +Line 2: More text. + +Line 3: Even more text. + +Line 4: Final line of text to test rendering. +\end{document} diff --git a/test/tikz-shading-test.tex b/test/tikz-shading-test.tex new file mode 100644 index 00000000..4d9c0199 --- /dev/null +++ b/test/tikz-shading-test.tex @@ -0,0 +1,46 @@ +\documentclass[12pt]{article} +\usepackage{tikz} +\usepackage{pgfplots} +\pgfplotsset{compat=1.18} + +\begin{document} + +\section*{Shading Test 1: Simple axial shade} + +\begin{tikzpicture} + % Simple axial shading: left=red, right=blue + \shade[left color=red, right color=blue] (0,0) rectangle (5,3); +\end{tikzpicture} + +\section*{Shading Test 2: Radial shade} + +\begin{tikzpicture} + % Simple radial shading: inner=yellow, outer=black + \shade[inner color=yellow, outer color=black] (0,0) circle (2); +\end{tikzpicture} + +\section*{Shading Test 3: Ball shade} + +\begin{tikzpicture} + % Ball shading + \shade[ball color=blue] (0,0) circle (2); +\end{tikzpicture} + +\section*{Shading Test 4: Axis shading (pgfplots)} + +\begin{tikzpicture} + \begin{axis}[xmin=0, xmax=1, ymin=0, ymax=1] + \addplot[fill=blue, fill opacity=0.5] coordinates {(0,0) (0.5,0.8) (1,0)}; + \addplot[fill=red, fill opacity=0.5] coordinates {(0,0) (0.5,0.4) (1,0)}; + \end{axis} +\end{tikzpicture} + +\section*{Shading Test 5: Multiple color stops} + +\begin{tikzpicture} + % Multi-color shading + \shade[top color=red, bottom color=blue, middle color=yellow] + (0,0) rectangle (5,3); +\end{tikzpicture} + +\end{document} diff --git a/test/tikz-supported.tex b/test/tikz-supported.tex new file mode 100644 index 00000000..59040ad6 --- /dev/null +++ b/test/tikz-supported.tex @@ -0,0 +1,605 @@ +\documentclass[12pt]{article} +\usepackage{tikz} +\usetikzlibrary{arrows.meta, shapes, calc, backgrounds, positioning, patterns, decorations.markings, decorations.pathmorphing, decorations.pathreplacing, decorations.text, fit, matrix, shadings} +\usepackage{xcolor} +\usepackage{graphicx} + +\begin{document} + +\section*{TeXpresso TikZ Support Test} + +\subsection*{Phase 0: Dash Patterns (long pattern > 4 elements)} + +\begin{tikzpicture} + % 5 element dash pattern + \draw[dash pattern=on 4pt off 2pt on 8pt off 3pt on 12pt off 4pt] (0,0) -- (8,0); + \draw[dash pattern=on 5pt off 3pt on 7pt off 4pt on 9pt off 2pt on 3pt off 1pt] (0,-0.5) -- (8,-0.5); +\end{tikzpicture} + +\subsection*{Phase 1: Text Nodes} + +\begin{tikzpicture} + % basic node labels + \node[draw, fill=yellow!30] at (0,0) {Hello TikZ}; + \node[draw, fill=blue!20, rounded corners] at (4,0) {Node B}; + \node[draw, circle, fill=red!20] at (8,0) {X}; + + % different font size nodes + \node[draw] at (0,2) {\tiny tiny}; + \node[draw] at (2,2) {\small small}; + \node[draw] at (4,2) {\large large}; + \node[draw] at (6,2) {\huge huge}; + + % multi-line node (align) + \node[draw, align=center] at (4,-2) {Multi-line\\node text}; +\end{tikzpicture} + +\subsection*{Phase 1: Basic Paths (supported)} + +\begin{tikzpicture} + % lines + \draw[blue, thick] (0,0) -- (3,0); + % curves (c, v, y) + \draw[red, thick] (0,-0.5) .. controls (1,-1.5) and (2,-1.5) .. (3,-0.5); + \draw[green!60!black, thick] (0,-1) .. controls (2,-2) .. (3,-1); + \draw[orange, thick] (0,-1.5) .. controls (2,-2) and (2,-2) .. (3,-1.5); + % rectangle + \draw[purple, thick] (0,-2) rectangle (3,-3); + % circle and ellipse + \draw[teal, thick] (5,0) circle (1); + \draw[brown, thick] (5,-1.5) ellipse (1.5 and 0.8); +\end{tikzpicture} + +\subsection*{Phase 1: Fill and Stroke} + +\begin{tikzpicture} + % fill only + \fill[red!50] (0,0) circle (0.9); + % draw only + \draw[blue, line width=2pt] (2,0) circle (0.8); + % fill + draw + \filldraw[fill=green!40, draw=green!80!black, line width=1.5pt] (4,0) circle (0.8); + + % rectangles + \fill[cyan!40] (6,-0.5) rectangle (7.5,0.5); + \draw[cyan!80!black, line width=2pt] (8,-0.5) rectangle (9.5,0.5); +\end{tikzpicture} + +\subsection*{Phase 1: Line Types (width, join, cap, dash)} + +\begin{tikzpicture} + % line width + \draw[line width=0.5pt] (0,0) -- (2,0) node[right] {0.5pt}; + \draw[line width=2pt] (0,-0.5) -- (2,-0.5) node[right] {2pt}; + \draw[line width=5pt] (0,-1) -- (2,-1) node[right] {5pt}; + + % line join + \draw[line width=3pt, line join=miter] (4,0) -- (5,0.5) -- (6,0); + \draw[line width=3pt, line join=round] (4,-0.5) -- (5,0) -- (6,-0.5); + \draw[line width=3pt, line join=bevel] (4,-1) -- (5,-0.5) -- (6,-1); + + % line cap + \draw[line width=4pt, line cap=butt, red] (8,0) -- (10,0); + \draw[line width=4pt, line cap=round, green!60!black] (8,-0.5) -- (10,-0.5); + \draw[line width=4pt, line cap=rect, blue] (8,-1) -- (10,-1); + + % dash patterns + \draw[line width=1pt, dash pattern=on 3pt off 2pt] (0,-2) -- (3,-2); + \draw[line width=1pt, dash pattern=on 2pt off 1pt on 4pt off 2pt] (4,-2) -- (7,-2); + \draw[line width=1pt, dash pattern=on 4pt off 2pt on 1pt off 2pt on 8pt off 3pt] (8,-2) -- (10,-2); +\end{tikzpicture} + +\subsection*{Phase 1: Color Modes} + +\begin{tikzpicture} + % named colors + \fill[red] (0,0) circle (0.5) node[right] {red}; + \fill[green!60!black] (0,-1) circle (0.5) node[right] {green}; + \fill[blue] (0,-2) circle (0.5) node[right] {blue}; + + % mixed colors + \fill[orange] (5,0) circle (0.5) node[right] {orange}; + \fill[purple] (5,-1) circle (0.5) node[right] {purple}; + \fill[teal] (5,-2) circle (0.5) node[right] {teal}; + + % gray + \fill[black!30] (10,0) circle (0.5) node[right] {30\% black}; + \fill[black!70] (10,-1) circle (0.5) node[right] {70\% black}; +\end{tikzpicture} + +\subsection*{Phase 1: Graphics State (q/Q, cm, W)} + +\begin{tikzpicture} + % q/Q: save/restore state + \begin{scope}[red, line width=2pt] + \draw (0,0) -- (2,0); + \begin{scope}[blue, line width=1pt] + \draw (0,-0.5) -- (2,-0.5); + \fill[green!30] (3,-0.5) circle (0.5); + \end{scope} + \draw (0,-1) -- (2,-1); + \end{scope} + + % cm: transform + \begin{scope}[shift={(6,0)}] + \draw[->] (0,0) -- (1,0) node[right] {shifted}; + \end{scope} + + % rotation + \begin{scope}[shift={(6,-1)}, rotate=30] + \draw[->, thick] (0,0) -- (1,0) node[right] {rotated}; + \end{scope} + + % scale + \begin{scope}[shift={(6,-2)}, scale=1.5] + \draw (0,0) rectangle (0.5,0.5) node[right] {scaled}; + \end{scope} + + % clip (W) + \begin{scope} + \clip (0,-3) rectangle (3,-1.5); + \fill[red!30] (1,-3.5) circle (1); + \fill[blue!30] (2,-2.5) circle (0.8); + \draw[thick] (0,-3) rectangle (3,-1.5); + \end{scope} +\end{tikzpicture} + +\subsection*{Phase 1: Path Operators (m, l, c, v, y, h, re)} + +\begin{tikzpicture} + % m (moveto) + l (lineto): \draw + \draw (0,0) -- (1,0.5) -- (2,0) -- (3,0.5); + % h (closepath): -- cycle + \draw[fill=blue!20] (0,-1) -- (2,-1) -- (2,-2) -- (0,-2) -- cycle; + % re (rectangle) + \draw[fill=red!20] (4,-1) rectangle (6,-2); + % combined: v + y (curves) + \draw[thick] (8,0) to[out=0, in=180] (10,-1); +\end{tikzpicture} + +\subsection*{Phase 4: Alpha / Transparency} + +\begin{tikzpicture} + % fill opacity + \fill[red, fill opacity=0.5] (0,0) circle (1); + \fill[blue, fill opacity=0.5] (1,0) circle (1); + \fill[green!60!black, fill opacity=0.5] (0.5,-0.7) circle (1); + + % draw opacity + \draw[red, line width=4pt, draw opacity=0.3] (4,0) circle (1); + \draw[blue, line width=4pt, draw opacity=0.3] (5,0) circle (1); + + % different opacity levels + \foreach \i/\op in {0/0.1, 0.7/0.3, 1.4/0.5, 2.1/0.7, 2.8/0.9} { + \fill[black, fill opacity=\op] (\i+8,-0.5) rectangle (\i+8.5,0.5); + } +\end{tikzpicture} + +\subsection*{Phase 5: Marked Content (BMC/BDC/EMC - no-op) and Decorations} + +\begin{tikzpicture} + % BMC/BDC/EMC test: layers (tikz internal) + % decorations.markings (pathmorphing may also use marked content) + \draw[decorate, decoration={zigzag, amplitude=3pt}] (0,0) -- (5,0); + \draw[decorate, decoration={snake, amplitude=2pt}] (0,-0.5) -- (5,-0.5); + + % decorations.markings + \draw[postaction={decorate, decoration={markings, mark=at position 0.5 with {\arrow{>}}}}, + thick] (0,-1.5) -- (5,-1.5); + \draw[postaction={decorate, decoration={markings, mark=at position 0.3 with {\arrow{>}}, + mark=at position 0.7 with {\arrow{<}}}}, + thick] (0,-2) -- (5,-2); + + % shapes + \node[draw, diamond, fill=yellow!20] at (8,0) {Diamond}; + \node[draw, trapezium, fill=green!20] at (8,-1) {Trapezium}; + \node[draw, cylinder, shape border rotate=90, fill=red!20] at (8,-2) {Cylinder}; +\end{tikzpicture} + +\subsection*{Combined Test: Complex Graphics with Text} + +\begin{tikzpicture} + % flow chart + \node[draw, rounded corners, fill=blue!10, line width=1.2pt] + (start) at (0,0) {Start}; + \node[draw, diamond, fill=yellow!10, aspect=2, line width=1.2pt] + (check) at (3,-2) {Check?}; + \node[draw, fill=green!10, line width=1.2pt] + (yes) at (6,-3.5) {Yes branch}; + \node[draw, fill=red!10, line width=1.2pt] + (no) at (0,-3.5) {No branch}; + + \draw[->, thick, dash pattern=on 3pt off 2pt] (start) -- (check); + \draw[->, thick] (check) -- (yes) node[midway, right] {Yes}; + \draw[->, thick] (check) -- (no) node[midway, left] {No}; + + % transparency overlay + \fill[blue!30, fill opacity=0.3] (-1,-5) rectangle (7,1); +\end{tikzpicture} + +\subsection*{Combined Test: Coordinate System and Labels} + +\begin{tikzpicture}[scale=1.2] + % coordinate axes + \draw[->, thick] (-1,0) -- (5,0) node[below] {$x$}; + \draw[->, thick] (0,-1) -- (0,4) node[left] {$y$}; + + % curve + \draw[blue, thick, fill=blue!10, fill opacity=0.5] + (0,0) .. controls (1,2) and (3,3) .. (4,1) -- cycle; + + % label points + \node[circle, fill=red, inner sep=1.5pt, label=above left:{$P$}] at (1,1.5) {}; + \node[circle, fill=red, inner sep=1.5pt, label=right:{$Q$}] at (3,2) {}; + + % dashed control lines + \draw[dashed, dash pattern=on 2pt off 2pt, gray] (0,0) -- (1,2); + \draw[dashed, dash pattern=on 2pt off 2pt on 6pt off 3pt, gray] (1,2) -- (3,3); + \draw[dashed, dash pattern=on 3pt off 1.5pt on 1pt off 1.5pt, gray] (3,3) -- (4,1); +\end{tikzpicture} + +\subsection*{Phase 6: Arrows and Advanced Node Placement} + +\begin{tikzpicture} + % various arrow tips + \draw[->] (0,0) -- (2,0) node[right] {default}; + \draw[->, thick] (0,-0.5) -- (2,-0.5) node[right] {thick}; + \draw[->, thin] (0,-1) -- (2,-1) node[right] {thin}; + \draw[->, dashed] (0,-1.5) -- (2,-1.5) node[right] {dashed}; + + % different arrow styles + \draw[->>] (4,0) -- (6,0) node[right] {->>}; + \draw[->>] (4,-0.5) -- (6,-0.5) node[right] {->>}; + + % node positioning with named anchors + \node[draw] (A) at (0,-3) {Node A}; + \node[draw] (B) at (3,-3) {Node B}; + \draw[->] (A) -- (B); + \node[draw, below=0.5cm of A] {Below A}; + \node[draw, right=0.5cm of B] {Right B}; + + % curved arrows + \node[draw] (C) at (0,-5) {C}; + \node[draw] (D) at (3,-5) {D}; + \draw[->, bend left] (C) to (D); + \draw[->, bend right] (C) to (D); +\end{tikzpicture} + +\subsection*{Phase 7: Resizebox / Scaling} + +% Test resizebox: shrink a tikzpicture to 40% of text width +{ + \centering + \resizebox{0.4\textwidth}{!}{% + \begin{tikzpicture} + \fill[blue!20] (0,0) rectangle (5,2); + \draw[blue, thick] (0,0) rectangle (5,2); + \node[font=\large\bfseries] at (2.5,1) {Resized 0.4x}; + \end{tikzpicture}% + } +} + +\subsection*{Phase 8: Complex Path Operations} + +\begin{tikzpicture} + % arc + \draw[thick] (0,0) arc (0:180:2); + \draw[thick, dashed] (4,0) arc (0:-180:2); + + % grid + \draw[gray, very thin] (0,-2) grid (3,-0.5); + \draw[blue] (0,-2) rectangle (3,-0.5); + + % parabola + \draw[thick] (5,0) parabola bend (6,2) (7,0); + \draw[thick, red] (5,-1) parabola (7,-0.5); + + % sin / cos (via plot coordinates) + \draw[blue, thick] plot coordinates {(0,-2) (0.5,-1.5) (1,-1) (1.5,-0.5) (2,0)}; + + % to with various options + \draw (8,0) to[out=45, in=135] (10,0); + \draw (8,-0.5) to[bend left] (10,-0.5); + \draw (8,-1) to[out=-45, in=-135, looseness=2] (10,-1); +\end{tikzpicture} + +\subsection*{Phase 9: Matrix and Fit} + +\begin{tikzpicture} + % matrix + % grid of named nodes (avoids \& issues with matrix) + \node[draw] (m11) at (0,0) {A}; + \node[draw] (m12) at (2.5,0) {B}; + \node[draw] (m13) at (5,0) {C}; + \node[draw] (m21) at (0,-1) {D}; + \node[draw] (m22) at (2.5,-1) {E}; + \node[draw] (m23) at (5,-1) {F}; + \node[draw] (m31) at (0,-2) {G}; + \node[draw] (m32) at (2.5,-2) {H}; + \node[draw] (m33) at (5,-2) {I}; + \node[draw, fit=(m11)(m13), fill=red!10] {}; + \node[draw, fit=(m22)(m33), fill=blue!10] {}; +\end{tikzpicture} + +\subsection*{Phase 10: Foreach and Calculations} + +\begin{tikzpicture} + % foreach loop + \foreach \x in {0,0.5,...,3} { + \draw[gray] (\x,0) -- (\x,2); + } + + % concentric circles + \foreach \r in {0.25,0.5,0.75,1,1.25,1.5} { + \draw (4,1) circle (\r); + } + + % radial lines using foreach + \foreach \angle in {0,30,...,330} { + \draw (8,1) -- ++(\angle:1.5); + } + + % labeled points with foreach + \foreach \x/\c in {1/red, 2/blue, 3/green!60!black, 4/orange} { + \fill[\c] (\x*1.5-1,-2) circle (0.3) node[below] {\x}; + } +\end{tikzpicture} + +\subsection*{Phase 11: Edge Cases and Advanced Features} + +\begin{tikzpicture} + % opacity on text + \node[draw, fill=blue!20, fill opacity=0.3, text opacity=0.8] at (0,0) + {Faded text}; + + % double line + \draw[double, double distance=3pt] (4,-0.5) -- (7,-0.5) + node[right] {double line}; + + % pin + \node[circle, draw, pin=above:Pin text] at (0,-2) {Pin}; + + % label with distance + \node[draw, label={[red]90:Label}, label={[blue]-90:Bottom}] at (5,-2) {Labeled}; + + % shading (basic) + \shade[left color=red, right color=blue] (0,-3.5) rectangle (3,-2.5); + + % precision line join/cap cases + \draw[line width=6pt, blue!30] (4,-3) -- (4.5,-3.5); + \draw[line width=6pt, line cap=round, blue!60] (5.5,-3) -- (5,-3.5); + \draw[line width=6pt, line cap=round] (7,-3) -- (7,-3.5); + + % legend style via scope + \draw[red, thick] (8,-0.5) -- (9,-0.5) node[right] {Line 1}; + \draw[blue, thick, dashed] (8,-1) -- (9,-1) node[right] {Line 2}; + \draw (7.5,-2) rectangle (10,0.5); +\end{tikzpicture} + +\subsection*{Phase 12: Shading Variants} + +\begin{tikzpicture} + % horizontal shading (top to bottom) + \shade[top color=yellow, bottom color=orange] (0,0) rectangle (3,1.5); + + % tricolor shading + \shade[left color=white, right color=black!40] (4,0) rectangle (7,1.5); + + % vertical + horizontal combo + \shade[top color=green!40, bottom color=blue!40, + left color=green!40, right color=blue!40] + (8,0) rectangle (11,1.5); + + % inner color / outer color (radial-like axial) + \shade[inner color=red!30, outer color=blue!70] + (0,-2.5) circle (1); + + \shade[inner color=yellow!60, outer color=black!20] + (4,-2.5) ellipse (1.5 and 0.8); + + % shading with opacity + \shade[top color=red, bottom color=blue, opacity=0.6] + (7,-3) rectangle (10,-1.5); +\end{tikzpicture} + +\subsection*{Phase 13: Patterns and Decoration Extras} + +\begin{tikzpicture} + % pattern fill (crosshatch) + \fill[pattern=crosshatch, pattern color=red!50] + (0,0) rectangle (2.5,1.5); + \draw[thick] (0,0) rectangle (2.5,1.5); + + % pattern fill (horizontal lines) + \fill[pattern=horizontal lines, pattern color=blue!50] + (3,0) rectangle (5.5,1.5); + \draw[thick] (3,0) rectangle (5.5,1.5); + + % pattern fill (north east lines) + \fill[pattern=north east lines, pattern color=green!60!black] + (6,0) rectangle (8.5,1.5); + \draw[thick] (6,0) rectangle (8.5,1.5); + + % pattern fill (dots) + \fill[pattern=dots, pattern color=purple] + (9,0) rectangle (11.5,1.5); + \draw[thick] (9,0) rectangle (11.5,1.5); + + % decoration: brace + \draw[decorate, decoration={brace, amplitude=6pt}] + (0,-1) -- (4,-1) node[midway, below=8pt] {brace}; + + % decoration: text along path + \draw[decorate, decoration={text along path, + text={A wavy path}}] (0,-2.5) sin (2,-2) cos (4,-2.5) sin (6,-2); + + % more complex decoration + \draw[very thick, decorate, decoration={ + zigzag, segment length=6pt, amplitude=3pt}] + (7,-3) -- (11,-3); +\end{tikzpicture} + +\subsection*{Phase 14: Advanced Path Connectors} + +\begin{tikzpicture} + % orthogonal connectors + \node[draw] (A) at (0,0) {A}; + \node[draw] (B) at (2,1.5) {B}; + \draw[->] (A) |- (B); + \draw[->] (A) -| (B); + + % various to-paths + \node[draw] (C) at (5,0) {C}; + \node[draw] (D) at (8,1.5) {D}; + \draw[->, to path={-- (\tikztotarget)}] (C) -- (D); + + \node[draw] (E) at (5,-2) {E}; + \node[draw] (F) at (8,-0.5) {F}; + \draw[->] (E) to[out=60, in=180] (F); + + \node[draw] (G) at (5,-3.5) {G}; + \node[draw] (H) at (8,-3.5) {H}; + \draw[->] (G) to[bend left=45] (H); + \draw[->] (H) to[bend left=45] (G); + + % edge with label + \node[draw] (I) at (10,0) {I}; + \node[draw] (J) at (12,1) {J}; + \draw (I) edge[->, bend left] node[right] {label} (J); + + % loop + \node[draw] (K) at (0,-3) {K}; + \draw[->] (K) edge[loop above] (); + \draw[->] (K) edge[loop left] (); +\end{tikzpicture} + +\subsection*{Phase 15: Node Anchor and Positioning} + +\begin{tikzpicture} + % named anchors + \node[draw, shape=rectangle] (R) at (2,0) + {\shortstack{anchor\\test}}; + \fill[red] (R.north) circle (1.5pt) node[above] {n}; + \fill[red] (R.south) circle (1.5pt) node[below] {s}; + \fill[red] (R.east) circle (1.5pt) node[right] {e}; + \fill[red] (R.west) circle (1.5pt) node[left] {w}; + \fill[red] (R.north east) circle (1.5pt); + \fill[red] (R.north west) circle (1.5pt); + \fill[red] (R.south east) circle (1.5pt); + \fill[red] (R.south west) circle (1.5pt); + + % positioning with angles + \node[draw] (S) at (7,0) {Source}; + \node[draw, above right=1cm and 0.5cm of S] {Above Right}; + \node[draw, below left=0.8cm and 0.3cm of S] {Below Left}; + + % chain-like positioning + \node[draw] (X0) at (2,-3.5) {X0}; + \node[draw, right=0.5cm of X0] (X1) {X1}; + \node[draw, right=0.5cm of X1] (X2) {X2}; + \draw[->] (X0) -- (X1) -- (X2); + + % above/below with offsets + \node[draw] (P) at (8,-3) {P}; + \node[draw, above=0.3cm of P, xshift=8pt] {$Q_{up}$}; + \node[draw, below=0.5cm of P, xshift=-4pt] {$R_{dn}$}; +\end{tikzpicture} + +\subsection*{Phase 16: Foreach with Calculations} + +\begin{tikzpicture} + % nested foreach + \foreach \x in {1,...,4} { + \foreach \y in {1,...,3} { + \fill (\x*1.2, \y*1.2) circle (0.3); + } + } + + % foreach with evaluate + \foreach \i [evaluate=\i as \j using \i*2] in {1,...,5} { + \node[draw] at (\i*1.5+5, 0) {\j}; + } + + % count + \foreach \x [count=\c] in {a,b,c,d,e} { + \node[draw] at (\c*1.2+5, -2) {\x}; + } + + % remember + \foreach \x [remember=\x as \lastx (initially 0)] in {1,...,4} { + \node[draw, fill=yellow!20] at (\x*1.5+5, -4) {\lastx\,$\to$\,\x}; + } + + % foreach on named coordinates + \coordinate (base) at (2,-6); + \foreach \a/\r in {0/red, 60/green!60!black, 120/blue, 180/orange, 240/purple, 300/teal} { + \fill[\r] ($(base) + (\a:1.5)$) circle (0.3); + } +\end{tikzpicture} + +\subsection*{Phase 17: Scopes with Style Inheritance} + +\begin{tikzpicture} + % scoped opacity + \begin{scope}[transparency group, opacity=0.5] + \fill[red] (0,0) circle (0.8); + \fill[green] (0.6,0) circle (0.8); + \fill[blue] (0.3,0.5) circle (0.8); + \end{scope} + + % scoped transform + \begin{scope}[shift={(5,0)}, scale=1.3, rotate=15] + \draw[thick, ->] (0,0) -- (1,0) node[right] {$x$}; + \draw[thick, ->] (0,0) -- (0,1) node[above] {$y$}; + \node[draw, fill=white] at (0.5,0.5) {T}; + \end{scope} + + % scoped line style + \begin{scope}[very thick, color=teal, dashed] + \draw (0,-3) -- (3,-3); + \draw (0,-3.8) -- (3,-3.8); + \end{scope} + + % scoped fill with pattern + \begin{scope}[shift={(6,-2)}] + \fill[pattern=north east lines, pattern color=blue!30] + (0,-1) rectangle (3,1); + \draw[thick] (0,-1) rectangle (3,1); + \node at (1.5,0) {scope}; + \end{scope} +\end{tikzpicture} + +\subsection*{Phase 18: Coordinate Calculations} + +\begin{tikzpicture} + % basic calculation + \coordinate (A) at (0,0); + \coordinate (B) at (3,2); + \coordinate (C) at ($(A)!0.5!(B)$); + \fill (A) circle (2pt) node[below] {A}; + \fill (B) circle (2pt) node[right] {B}; + \fill[red] (C) circle (2pt) node[above] {C (mid)}; + + % point with offset + \coordinate (D) at ($(A)!0.3!(B) + (0,1)$); + \fill[blue] (D) circle (2pt) node[above] {D}; + + % projection + \coordinate (E) at ($(A)!(C)!(B)$); + \fill[green!60!black] (E) circle (2pt) node[below] {E}; + + % vector arithmetic + \coordinate (V) at (5,0); + \coordinate (W) at ($(V) + (2,1.5)$); + \draw[->, thick] (5,0) -- (W) node[midway, above] {$\vec{v}$}; + \draw[->, thick] (5,0) -- ($(5,0) + (0.5,2)$) + node[midway, left] {$\vec{w}$}; + + % polar coordinates + \foreach \angle in {0,45,...,315} { + \fill (\angle:1.5) circle (2pt); + } + \draw (0,0) circle (1.5); +\end{tikzpicture} + +\end{document}