├── test ├── incpath │ └── test.tex ├── README.md ├── simple.tex ├── include.tex ├── test_utf_mapping.c └── test_utf_mapping.output ├── doc ├── texpresso_logo_v2.png ├── ui-fr.md └── pres │ └── pres.tex ├── .gitmodules ├── src ├── dvi │ ├── Makefile.re2c │ ├── Makefile │ ├── fz_util.h │ ├── dvi_fonttable.c │ ├── fixed.h │ ├── pdf_lexer.h │ ├── dvi_scratch.c │ ├── mydvi_interp.h │ ├── tex_enc.c │ ├── dvi_context.c │ ├── intcodec.h │ ├── vstack.h │ ├── tex_vf.c │ ├── mydvi_opcodes.h │ └── tex_fontmap.c ├── prot_parser.c ├── logo.h ├── mupdf_compat.h ├── Makefile ├── myabort.h ├── prot_parser.h ├── logo.c ├── sexp_parser.h ├── incdvi.h ├── synctex.h ├── driver.h ├── myabort.c ├── json_parser.h ├── loader.c ├── editor.h ├── README.md ├── state.h ├── renderer.h ├── utf_mapping.h ├── engine_pdf.c ├── engine_dvi.c ├── proxy.c ├── sprotocol.h ├── fs.c ├── engine.h ├── incdvi.c ├── state.c ├── sexp_parser.c └── driver.c ├── .gitignore ├── .clang-format ├── Makefile.tectonic ├── scripts └── texpresso-debug ├── mupdf-config.sh ├── LICENSE ├── CHANGELOG.md ├── emacs └── texpresso-map.el ├── Makefile ├── SERVER-PROTOCOL.md ├── INSTALL.md ├── README.md └── EDITOR-PROTOCOL.md /test/incpath/test.tex: -------------------------------------------------------------------------------- 1 | Hello from file in include path! 2 | -------------------------------------------------------------------------------- /doc/texpresso_logo_v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/let-def/texpresso/HEAD/doc/texpresso_logo_v2.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tectonic"] 2 | path = tectonic 3 | branch = master 4 | url = https://github.com/let-def/tectonic.git 5 | -------------------------------------------------------------------------------- /src/dvi/Makefile.re2c: -------------------------------------------------------------------------------- 1 | SOURCES = $(wildcard *.re2c.c) 2 | TARGETS = $(patsubst %.re2c.c,%.c,$(SOURCES)) 3 | 4 | all: $(TARGETS) 5 | 6 | %.c: %.re2c.c 7 | re2c $< -o $@ --tags --bit-vectors 8 | 9 | .PHONY: all 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | share 3 | driver/proxy 4 | .gdb_history 5 | .cache 6 | *.log 7 | *.dSYM 8 | *.so 9 | *.o 10 | texpresso-dev 11 | texpresso 12 | compile_commands.json 13 | Makefile.config 14 | .DS_Store 15 | test/test_utf_mapping 16 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | [simple.tex](simple.tex) (`make simple`): sample .tex file to check that basic features are working as expected 2 | 3 | [include.tex](include.tex) (`make include`): a .tex file to test include path support, it needs `-I incpath` to be passed to TeXpresso to build correctly. 4 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Chromium 3 | IndentWidth: 2 4 | BreakBeforeBraces: Allman 5 | PointerAlignment: Right 6 | UseTab: Never 7 | --- 8 | Language: Cpp 9 | # Force pointers to the type for C++. 10 | DerivePointerAlignment: false 11 | # PointerAlignment: Left 12 | UseTab: Never 13 | -------------------------------------------------------------------------------- /Makefile.tectonic: -------------------------------------------------------------------------------- 1 | UNAME := $(shell uname) 2 | 3 | ifeq ($(UNAME), Darwin) 4 | CARGO_BUILD_FLAGS ?= --release --features external-harfbuzz 5 | endif 6 | ifeq ($(UNAME), Linux) 7 | CARGO_BUILD_FLAGS ?= --release --features external-harfbuzz 8 | endif 9 | 10 | tectonic: 11 | cd tectonic && $(TECTONIC_ENV) cargo build $(CARGO_BUILD_FLAGS) 12 | 13 | Makefile.config: 14 | $(MAKE) -f Makefile config 15 | include Makefile.config 16 | 17 | .PHONY: tectonic 18 | -------------------------------------------------------------------------------- /scripts/texpresso-debug: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | rm -f "$TMPDIR/texpresso.stdin" "$TMPDIR/texpresso.stdout" "$TMPDIR/texpresso.stderr" 3 | mkfifo "$TMPDIR/texpresso.stdin" "$TMPDIR/texpresso.stdout" "$TMPDIR/texpresso.stderr" 4 | cat >"$TMPDIR/texpresso.lldb" < 0$. 21 | 22 | \end{document} 23 | -------------------------------------------------------------------------------- /test/include.tex: -------------------------------------------------------------------------------- 1 | \documentclass[12pt]{article} 2 | 3 | \begin{document} 4 | 5 | This is a very simple file, though it does include some mathematical 6 | symbols, $\beta, x$ and $y$, and some equations, 7 | \begin{equation} 8 | \frac{1}{2} + \frac{1}{5} = \frac{7}{10}. 9 | \end{equation} 10 | 11 | The equations are automatically numbered: 12 | \begin{equation} 13 | 1 + 1 = 2 \Rightarrow E = m c^2. 14 | \end{equation} 15 | 16 | \LaTeX\ can handle complicated mathematical expressions. 17 | \begin{equation} 18 | \int_0^\infty \cos (k t) e^{-s t} d t = \frac{s}{s^2 + k^2} 19 | \end{equation} 20 | if $s > 0$. 21 | \input{test.tex} 22 | \end{document} 23 | -------------------------------------------------------------------------------- /src/prot_parser.c: -------------------------------------------------------------------------------- 1 | #include "prot_parser.h" 2 | 3 | void prot_reinitialize(prot_parser *cp) 4 | { 5 | if (cp->is_json) 6 | cp->state.json = initial_json_parser; 7 | else 8 | cp->state.sexp = initial_sexp_parser; 9 | } 10 | 11 | void prot_initialize(prot_parser *cp, int is_json) 12 | { 13 | cp->is_json = is_json; 14 | prot_reinitialize(cp); 15 | } 16 | 17 | const char *prot_parse(fz_context *ctx, prot_parser *cp, vstack *stack, const 18 | char *input, const char *limit) 19 | { 20 | if (cp->is_json) 21 | return json_parse(ctx, &cp->state.json, stack, input, limit); 22 | else 23 | return sexp_parse(ctx, &cp->state.sexp, stack, input, limit); 24 | } 25 | -------------------------------------------------------------------------------- /mupdf-config.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | FLAGS=$@ 4 | 5 | mkdir -p build 6 | 7 | cat >build/mupdf_test.c < /dev/null; then 21 | LINKING="$LINKING $i" 22 | printf " %s" "$i" 23 | else 24 | SKIPPING="$SKIPPING $i" 25 | fi 26 | done 27 | } 28 | 29 | link_if_possible -lmupdf-third -lleptonica -ltesseract -lmujs -lgumbo 30 | 31 | if [ -n "$LINKING" ]; then 32 | echo >&2 "Linking$LINKING" 33 | fi 34 | if [ -n "$SKIPPING" ]; then 35 | echo >&2 "Skipping$SKIPPING" 36 | fi 37 | 38 | rm -f build/mupdf_test* 39 | 40 | exit 0 41 | -------------------------------------------------------------------------------- /src/dvi/Makefile: -------------------------------------------------------------------------------- 1 | OBJECTS= \ 2 | dvi_context.o dvi_interp.o dvi_prim.o dvi_special.o \ 3 | dvi_scratch.o dvi_fonttable.o dvi_resmanager.o \ 4 | tex_tfm.o tex_fontmap.o tex_vf.o tex_enc.o \ 5 | vstack.o pdf_lexer.o 6 | 7 | BUILD=../../build 8 | DIR=$(BUILD)/objects 9 | 10 | DIR_OBJECTS=$(foreach OBJ,$(OBJECTS),$(DIR)/$(OBJ)) 11 | 12 | all: $(DIR)/libmydvi.a 13 | 14 | $(DIR)/libmydvi.a: $(DIR_OBJECTS) 15 | ar cr $@ $^ 16 | 17 | $(DIR)/dvi_resmanager.o: dvi_resmanager.c 18 | $(CC) $(shell pkg-config --cflags freetype2) -c -o $@ $< 19 | 20 | $(DIR)/%.o: %.c 21 | $(CC) -c -o $@ $< 22 | 23 | $(DIR)/%.o: $(DIR)/%.c 24 | $(CC) -c -o $@ $< 25 | 26 | %.c: %.re2c.c 27 | @ if test $@ -ot $^; then \ 28 | echo -e >&2 "\033[0;33mre2c: $@ is older than $^ or missing"; \ 29 | echo -e >&2 " consider running 'make re2c'\033[0m"; \ 30 | fi 31 | 32 | re2c: 33 | $(MAKE) -f Makefile.re2c all 34 | 35 | clean: 36 | rm -f $(DIR)/libmydvi.a $(DIR_OBJECTS) 37 | 38 | .PHONY: all clean re2c 39 | 40 | ../../Makefile.config: 41 | $(MAKE) -C ../.. config 42 | include ../../Makefile.config 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Frédéric Bour 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /doc/ui-fr.md: -------------------------------------------------------------------------------- 1 | # Utilisation 2 | 3 | Depuis Emacs, il faut ouvrir un fichier .tex et activer `texpresso`. 4 | 5 | À l'activation, il demande de choisir le fichier .tex racine. Pour un fichier auto-contenu, on peut juste valider pour utiliser le fichier courant. Pour un fichier multi-projet, on peut naviguer pour choisir la racine. 6 | 7 | La fenêtre TeXpresso s'ouvre. 8 | Contrôles claviers : 9 | 10 | - ⬅️ gauche et ➡️ droite : changer de page 11 | - `p` (pour "page") : changer le zoom entre "Pleine page" et "Pleine largeur" 12 | - `c` (pour "crop") : enlever les bordures vides de la page 13 | - `q` (pour "quit") : quitter 14 | - `i` (pour "invert") : mode nuit 15 | - `I` : utiliser le thème emacs (pour l'instant c'est juste le mien :D) 16 | - `t` (pour "top") : garder la fenêtre au premier plan (au-dessus d'emacs donc) 17 | - `b` (pour "border") : activer/désactiver les bordures de fenêtres 18 | - `r` (pour "reload") : ne pas appuyer :D, c'est une fonctionnalité de "hot-reload" pour debug, mais ça risque de crasher 19 | 20 | Contrôles souris : 21 | 22 | - clique : tente de positionner le buffer emacs autour de la ligne sélectionnée 23 | - contrôle + clique : faire défiler la page 24 | - molette : faire défiler la page 25 | - contrôle + molette : zoom 26 | -------------------------------------------------------------------------------- /src/logo.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #ifndef LOGO_H 26 | #define LOGO_H 27 | 28 | #include 29 | 30 | SDL_Surface *texpresso_logo(void); 31 | 32 | #endif /*!LOGO_H*/ 33 | -------------------------------------------------------------------------------- /src/mupdf_compat.h: -------------------------------------------------------------------------------- 1 | #ifndef __MUPDF_COMPAT__ 2 | #define __MUPDF_COMPAT__ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // fz_malloc_struct_array, fz_irect_width and fz_irect_height 10 | // were introduced in mupdf 1.19.0 11 | 12 | #if (FZ_VERSION_MAJOR == 1) && (FZ_VERSION_MINOR < 19) 13 | 14 | #define fz_malloc_struct_array(CTX, N, TYPE) \ 15 | ((TYPE*)Memento_label(fz_calloc(CTX, N, sizeof(TYPE)), #TYPE "[]")) 16 | 17 | static inline unsigned int 18 | fz_irect_width(fz_irect r) 19 | { 20 | unsigned int w; 21 | if (r.x0 >= r.x1) 22 | return 0; 23 | return (int)(r.x1 - r.x0); 24 | } 25 | 26 | static inline unsigned int 27 | fz_irect_height(fz_irect r) 28 | { 29 | unsigned int w; 30 | if (r.y0 >= r.y1) 31 | return 0; 32 | return (int)(r.y1 - r.y0); 33 | } 34 | 35 | #endif 36 | 37 | #if (FZ_VERSION_MAJOR == 1) && (FZ_VERSION_MINOR == 16) 38 | 39 | // Before mupdf 1.17.0, fz_buffer was defined as 40 | // typedef struct fz_buffer_s fz_buffer; 41 | // and fz_buffer_s was kept opaque. 42 | 43 | struct fz_buffer_s 44 | { 45 | int refs; 46 | unsigned char *data; 47 | size_t cap, len; 48 | int unused_bits; 49 | int shared; 50 | }; 51 | 52 | // fz_open_file_ptr_no_close is implemented but not exposed by mupdf 1.16 53 | fz_stream *fz_open_file_ptr_no_close(fz_context *ctx, FILE *file); 54 | 55 | #endif 56 | 57 | #endif /*!__MUPDF_COMPAT__*/ 58 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | OBJECTS=sprotocol.o state.o fs.o incdvi.o myabort.o renderer.o engine_tex.o engine_pdf.o engine_dvi.o synctex.o prot_parser.o sexp_parser.o json_parser.o editor.o 2 | 3 | BUILD=../build 4 | DIR=$(BUILD)/objects 5 | 6 | DIR_OBJECTS=$(foreach OBJ,$(OBJECTS),$(DIR)/$(OBJ)) 7 | TARGETS=texpresso texpresso-dev texpresso-debug-proxy texpresso.so 8 | 9 | all: $(TARGETS) 10 | 11 | texpresso: $(BUILD)/texpresso 12 | $(BUILD)/texpresso: $(DIR)/driver.o $(DIR)/main.o $(DIR)/logo.o $(DIR_OBJECTS) $(DIR)/libmydvi.a 13 | $(LDCC) -o $@ $^ $(LIBS) 14 | 15 | texpresso-dev: $(BUILD)/texpresso-dev 16 | $(BUILD)/texpresso-dev: $(DIR)/driver.o $(DIR)/loader.o $(DIR)/logo.o | $(BUILD)/texpresso-dev.so 17 | $(LDCC) -o $@ $^ $(LIBS) 18 | 19 | texpresso-dev.so: $(BUILD)/texpresso-dev.so 20 | $(BUILD)/texpresso-dev.so: $(DIR)/main.o $(DIR_OBJECTS) $(DIR)/libmydvi.a 21 | $(MAKE) -C dvi 22 | $(LDCC) -ldl -shared -o $@ $^ $(LIBS) 23 | killall -SIGUSR1 texpresso-dev || true 24 | 25 | texpresso-debug-proxy: $(BUILD)/texpresso-debug-proxy 26 | $(BUILD)/texpresso-debug-proxy: proxy.c 27 | $(LDCC) -o $@ $^ 28 | 29 | texpresso-debug: $(BUILD)/texpresso-debug 30 | $(BUILD)/texpresso-debug: ../scripts/texpresso-debug 31 | cp $< $@ 32 | 33 | re2c: 34 | $(MAKE) -C dvi $@ 35 | 36 | $(DIR)/%.o: %.c 37 | $(CC) -c -o $@ -Idvi/ $< 38 | 39 | $(DIR)/libmydvi.a: $(wildcard dvi/*.*) 40 | $(MAKE) -C dvi 41 | 42 | clean: 43 | rm -f $(DIR)/*.o $(DIR)/*.a 44 | $(MAKE) -C dvi clean 45 | 46 | ../Makefile.config: 47 | $(MAKE) -C .. config 48 | include ../Makefile.config 49 | 50 | .PHONY: all clean $(TARGETS) re2c 51 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.1 Mon 11 Aug 14:42:38 JST 2025 2 | 3 | Hopefully the last release still relying on a custom build of tectonic: 4 | 5 | - Re-enable SyncTeX (accidentally disabled during a rebase) 6 | - Fix a crash when the same file was opened multiple times 7 | - Disable compositor bypass (#50, report by @adamkruszewski) 8 | - Handle infinite loops in TeX code 9 | - Fix race conditions when latest process observe data being changed 10 | - Workaround limitations of fork on macOS when using system fonts 11 | - Up/down scrolls vertically through the document, and change pages when reaching the border (#52, contributed by @gasche) 12 | - Fix JSON printing of non-ASCII characters (broke paths with chinese characters, see #53) 13 | - Fix a crash due to a broken invariant when the contents being edited has been read by the TeX worker but the driver is not aware (#62) 14 | - Don't require re2c for a non-developer build (#64) 15 | - Load local font/resources files from filesystem 16 | - Remove gumbo dependencies on macOS (change in homebrew packaging of mupdf) 17 | - On Linux, try Wayland video driver first to solve HIDPI issues 18 | - PDF engine: reload document on filesystem changes 19 | - Add `(input-file)` message to let editor track source files (# 20 | - Add `(change-range)` command to support change specified as UTF-16 code unit ranges, following LSP protocol (with help from @lnay) 21 | - New logo (contributed by @merv1n34k) 22 | - Build fixes for different platforms (various contributors) 23 | - PDF: fix parsing of indirect references, improve support for dashes in stroking operators (reported by @gasche) 24 | 25 | # v0.0 Fri Apr 5 06:52:52 JST 2024 26 | 27 | First publicly announced version of the project. 28 | Still alpha. 29 | -------------------------------------------------------------------------------- /src/myabort.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #ifndef __MYABORT_H__ 26 | #define __MYABORT_H__ 27 | 28 | #include 29 | 30 | __attribute__ ((noreturn)) 31 | void myabort_(const char *file, int line, const char *msg, uint32_t answer); 32 | 33 | void print_backtrace(void); 34 | 35 | #define myabort() myabort_(__FILE__, __LINE__, "", 42424242) 36 | #define myabort2(msg) myabort_(__FILE__, __LINE__, msg, 42424242) 37 | #define myabort3(msg, code) myabort_(__FILE__, __LINE__, msg, code) 38 | 39 | #endif /* __MYABORT_H__ */ 40 | -------------------------------------------------------------------------------- /emacs/texpresso-map.el: -------------------------------------------------------------------------------- 1 | (defvar texpresso--window-buffer nil) 2 | 3 | (defun texpresso--window-track (&rest r) 4 | (with-demoted-errors "Cannot move texpresso window: %S" 5 | (when (process-live-p texpresso--process) 6 | (let ((window (get-buffer-window texpresso--window-buffer)) 7 | rect x y w h) 8 | (if (not window) 9 | (texpresso--send 'stay-on-top nil) 10 | (setq rect (window-absolute-body-pixel-edges window)) 11 | (setq x (nth 0 rect)) 12 | (setq y (nth 1 rect)) 13 | ;; (when (and (frame-parameter nil 'fullscreen) 14 | ;; (not (frame-parameter nil 'undecorated))) 15 | ;; (setq y (- y (cdr (frame-position))))) 16 | (setq w (- (nth 2 rect) x)) 17 | (setq h (- (nth 3 rect) y)) 18 | ;; (message "rect %S %S %S %S" x y w h) 19 | (texpresso--send 'map-window x y w h)))))) 20 | 21 | (defun texpresso-window-map () 22 | (interactive) 23 | (setq texpresso--window-buffer (get-buffer-create "*texpresso-window*")) 24 | (display-buffer texpresso--window-buffer) 25 | (with-current-buffer texpresso--window-buffer 26 | (read-only-mode t) 27 | (add-hook 'window-configuration-change-hook #'texpresso--window-track) 28 | (add-hook 'move-frame-functions #'texpresso--window-track) 29 | (add-function :after after-focus-change-function #'texpresso--frame-focus) 30 | (texpresso--window-track))) 31 | 32 | (defun texpresso--frame-focus (&rest r) 33 | (when (and (process-live-p texpresso--process) 34 | (buffer-live-p texpresso--window-buffer)) 35 | (if (and (or (frame-focus-state) (frame-parameter nil 'fullscreen)) 36 | (get-buffer-window texpresso--window-buffer)) 37 | (texpresso--window-track) 38 | (texpresso--send 'unmap-window)))) 39 | -------------------------------------------------------------------------------- /src/prot_parser.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #ifndef PROT_PARSER_H_ 26 | #define PROT_PARSER_H_ 27 | 28 | #include "sexp_parser.h" 29 | #include "json_parser.h" 30 | 31 | 32 | typedef struct 33 | { 34 | int is_json; 35 | union 36 | { 37 | sexp_parser sexp; 38 | json_parser json; 39 | } state; 40 | } prot_parser; 41 | 42 | void prot_initialize(prot_parser *cp, int is_json); 43 | void prot_reinitialize(prot_parser *cp); 44 | const char *prot_parse(fz_context *ctx, prot_parser *cp, vstack *stack, const 45 | char *input, const char *limit); 46 | 47 | #endif // PROT_PARSER_H_ 48 | -------------------------------------------------------------------------------- /src/logo.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #include "logo.h" 26 | #define QOI_IMPLEMENTATION 27 | #define QOI_NO_STDIO 28 | #include "qoi.h" 29 | 30 | // move logo blob to separate file 31 | #include "logo_blob.inc" 32 | 33 | // void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) 34 | 35 | SDL_Surface *texpresso_logo(void) 36 | { 37 | qoi_desc desc; 38 | int channels = 4; 39 | void *pixels = qoi_decode(logo, logo_len, &desc, channels); 40 | 41 | if (!pixels) 42 | abort(); 43 | 44 | return SDL_CreateRGBSurfaceWithFormatFrom(pixels, desc.width, 45 | desc.height, 8 * channels, desc.width * channels, SDL_PIXELFORMAT_RGBA32); 46 | } 47 | 48 | -------------------------------------------------------------------------------- /src/dvi/fz_util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #ifndef FZ_UTIL_H 26 | #define FZ_UTIL_H 27 | 28 | #define fz_ptr(type, name) type *name = NULL; fz_var(name) 29 | #define fz_try_rethrow(ctx) fz_catch(ctx) { fz_rethrow(ctx); } 30 | 31 | static inline fz_matrix fz_post_translate(fz_matrix ctm, float tx, float ty) 32 | { 33 | ctm.e += tx; 34 | ctm.f += ty; 35 | return ctm; 36 | } 37 | 38 | static inline fz_matrix fz_flip_vertically(fz_matrix ctm) 39 | { 40 | ctm.b = -ctm.b; 41 | ctm.d = -ctm.d; 42 | ctm.f = ctm.f; 43 | return ctm; 44 | } 45 | 46 | static inline char *dtx_strndup(fz_context *ctx, const void *buf, size_t len) 47 | { 48 | char *result = fz_malloc_array(ctx, len + 1, char); 49 | memcpy(result, buf, len); 50 | result[len] = 0; 51 | return result; 52 | } 53 | 54 | #endif /*!FZ_UTIL_H*/ 55 | -------------------------------------------------------------------------------- /src/sexp_parser.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #ifndef SEXP_PARSER_H_ 26 | #define SEXP_PARSER_H_ 27 | 28 | #include "../dvi/vstack.h" 29 | 30 | enum sexp_parser_state 31 | { 32 | P_IDLE, 33 | P_IDENT, 34 | P_POS_NUMBER, 35 | P_NEG_NUMBER, 36 | P_POS_NUMBER_FRAC, 37 | P_NEG_NUMBER_FRAC, 38 | P_STRING, 39 | P_STRING_ESCAPE, 40 | P_STRING_OCTAL1, 41 | P_STRING_OCTAL2, 42 | }; 43 | 44 | typedef struct 45 | { 46 | enum sexp_parser_state state; 47 | union 48 | { 49 | int octal; 50 | struct 51 | { 52 | float number; 53 | float frac; 54 | }; 55 | }; 56 | } sexp_parser; 57 | 58 | extern const sexp_parser initial_sexp_parser; 59 | 60 | const char *sexp_parse(fz_context *ctx, sexp_parser *cp, vstack *stack, const 61 | char *input, const char *limit); 62 | 63 | #endif // SEXP_PARSER_H_ 64 | -------------------------------------------------------------------------------- /src/incdvi.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #ifndef INCDVI_H 26 | #define INCDVI_H 27 | 28 | #include 29 | #include 30 | #include "dvi/mydvi.h" 31 | 32 | typedef struct incdvi_s incdvi_t; 33 | 34 | incdvi_t *incdvi_new(fz_context *ctx, dvi_reshooks hooks); 35 | void incdvi_free(fz_context *ctx, incdvi_t *d); 36 | void incdvi_reset(incdvi_t *d); 37 | void incdvi_update(fz_context *ctx, incdvi_t *d, fz_buffer *buf); 38 | bool incdvi_output_started(incdvi_t *d); 39 | int incdvi_page_count(incdvi_t *d); 40 | void incdvi_page_dim(incdvi_t *d, fz_buffer *buf, int page, float *width, float *height, bool *landscape); 41 | void incdvi_render_page(fz_context *ctx, incdvi_t *d, fz_buffer *buf, int page, fz_device *dev); 42 | void incdvi_find_page_loc(fz_context *ctx, incdvi_t *d, fz_buffer *buf, int page); 43 | float incdvi_tex_scale_factor(incdvi_t *d); 44 | 45 | #endif /*!INCDVI_H*/ 46 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | $(MAKE) texpresso 3 | $(MAKE) texpresso-tonic 4 | @echo "# Build succeeded. Try running:" 5 | @echo "# build/texpresso test/simple.tex" 6 | 7 | texpresso: 8 | $(MAKE) -C src texpresso 9 | 10 | dev: 11 | $(MAKE) -C src texpresso-dev 12 | 13 | debug: 14 | $(MAKE) -C src texpresso-debug texpresso-debug-proxy 15 | 16 | clean: 17 | rm -rf build/objects/* 18 | 19 | distclean: 20 | rm -rf build Makefile.config 21 | cd tectonic && cargo clean 22 | 23 | re2c: 24 | $(MAKE) -C src $@ 25 | 26 | test-utfmapping: 27 | mkdir -p build 28 | gcc -g -o build/test_utf_mapping test/test_utf_mapping.c 29 | build/test_utf_mapping &> test/test_utf_mapping.output 30 | git diff --exit-code test/test_utf_mapping.output 31 | 32 | UNAME := $(shell uname) 33 | 34 | Makefile.config: Makefile 35 | $(MAKE) config 36 | 37 | ifeq ($(UNAME), Linux) 38 | config: 39 | mkdir -p build/objects 40 | # LDCC: some Linux distribution build mupdf with C++ dependencies, 41 | echo >Makefile.config "CFLAGS=-O2 -ggdb -I. -fPIC" 42 | echo >>Makefile.config 'CC=gcc $$(CFLAGS)' 43 | echo >>Makefile.config 'LDCC=g++ $$(CFLAGS)' 44 | echo >>Makefile.config "LIBS=-lmupdf -lm `CC=gcc ./mupdf-config.sh` -lz -ljpeg -ljbig2dec -lharfbuzz -lfreetype -lopenjp2 -lgumbo -lSDL2" 45 | echo >>Makefile.config "TECTONIC_ENV=" 46 | endif 47 | 48 | ifeq ($(UNAME), Darwin) 49 | BREW=$(shell brew --prefix) 50 | BREW_ICU4C=$(shell brew --prefix icu4c) 51 | config: 52 | mkdir -p build/objects 53 | echo >Makefile.config "CFLAGS=-O2 -ggdb -I. -fPIC -I$(BREW)/include" 54 | echo >>Makefile.config 'CC=gcc $$(CFLAGS)' 55 | echo >>Makefile.config 'LDCC=g++ $$(CFLAGS)' 56 | echo >>Makefile.config "LIBS=-L$(BREW)/lib -lmupdf -lm `CC=gcc ./mupdf-config.sh -L$(BREW)/lib` -lz -ljpeg -ljbig2dec -lharfbuzz -lfreetype -lopenjp2 -lSDL2" 57 | echo >>Makefile.config "TECTONIC_ENV=PKG_CONFIG_PATH=$(BREW_ICU4C)/lib/pkgconfig C_INCLUDE_PATH=$(BREW_ICU4C)/include LIBRARY_PATH=$(BREW_ICU4C)/lib" 58 | endif 59 | 60 | texpresso-tonic: 61 | $(MAKE) -f Makefile.tectonic tectonic 62 | cp -f tectonic/target/release/texpresso-tonic build/ 63 | 64 | .PHONY: all dev clean config texpresso-tonic re2c 65 | -------------------------------------------------------------------------------- /src/synctex.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #ifndef SYNCTEX_H_ 26 | #define SYNCTEX_H_ 27 | 28 | #include 29 | #include 30 | 31 | typedef struct synctex_s synctex_t; 32 | 33 | synctex_t *synctex_new(fz_context *ctx); 34 | void synctex_free(fz_context *ctx, synctex_t *stx); 35 | void synctex_rollback(fz_context *ctx, synctex_t *stx, size_t offset); 36 | void synctex_update(fz_context *ctx, synctex_t *stx, fz_buffer *buf); 37 | int synctex_page_count(synctex_t *stx); 38 | int synctex_input_count(synctex_t *stx); 39 | void synctex_page_offset(fz_context *ctx, synctex_t *stx, unsigned index, int *bop, int *eop); 40 | int synctex_input_offset(fz_context *ctx, synctex_t *stx, unsigned index); 41 | void synctex_scan(fz_context *ctx, synctex_t *stx, fz_buffer *buf, const char *doc_dir, unsigned page, int x, int y); 42 | 43 | int synctex_has_target(synctex_t *stx); 44 | void synctex_set_target(synctex_t *stx, int current_page, const char *path, int line); 45 | int synctex_find_target(fz_context *ctx, synctex_t *stx, fz_buffer *buf, int *page, int *x, int *y); 46 | 47 | #endif // SYNCTEX_H_ 48 | -------------------------------------------------------------------------------- /src/driver.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #ifndef DRIVER_H_ 26 | #define DRIVER_H_ 27 | 28 | #include 29 | #include 30 | #include 31 | #include "renderer.h" 32 | 33 | enum custom_events { 34 | SCAN_EVENT, 35 | RENDER_EVENT, 36 | RELOAD_EVENT, 37 | STDIN_EVENT, 38 | 39 | EVENT_COUNT, 40 | }; 41 | 42 | struct initial_state 43 | { 44 | int initialized; 45 | int page; 46 | int need_synctex; 47 | int zoom; 48 | txp_renderer_config config; 49 | fz_display_list *display_list; 50 | }; 51 | 52 | enum editor_protocol 53 | { 54 | EDITOR_SEXP, 55 | EDITOR_JSON, 56 | }; 57 | 58 | struct persistent_state { 59 | struct initial_state initial; 60 | enum editor_protocol protocol; 61 | int line_output; 62 | Uint32 custom_event; 63 | 64 | void (*schedule_event)(enum custom_events ev); 65 | bool (*should_reload_binary)(void); 66 | 67 | SDL_Window *window; 68 | SDL_Renderer *renderer; 69 | fz_context *ctx; 70 | 71 | const char *exe_path, *doc_path, *doc_name, *inclusion_path; 72 | }; 73 | 74 | bool texpresso_main(struct persistent_state *ps); 75 | 76 | #endif // DRIVER_H_ 77 | -------------------------------------------------------------------------------- /src/myabort.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #include "myabort.h" 26 | #include 27 | #include 28 | #include 29 | 30 | #define BT_BUF_SIZE 100 31 | 32 | void print_backtrace(void) 33 | { 34 | int nptrs; 35 | void *buffer[BT_BUF_SIZE]; 36 | char **strings; 37 | 38 | nptrs = backtrace(buffer, BT_BUF_SIZE); 39 | fprintf(stderr, "backtrace() returned %d addresses\n", nptrs); 40 | 41 | /* The call backtrace_symbols_fd(buffer, nptrs, STDOUT_FILENO) 42 | would produce similar output to the following: */ 43 | 44 | strings = backtrace_symbols(buffer, nptrs); 45 | if (strings == NULL) { 46 | perror("backtrace_symbols"); 47 | exit(EXIT_FAILURE); 48 | } 49 | 50 | for (int j = 0; j < nptrs; j++) 51 | fprintf(stderr, "%s\n", strings[j]); 52 | 53 | free(strings); 54 | } 55 | 56 | 57 | void myabort_(const char *file, int line, const char *msg, uint32_t code) 58 | { 59 | if (code == 42424242) 60 | fprintf(stderr, "Aborting from %s:%d (%s)\n", file, line, msg); 61 | else 62 | fprintf(stderr, "Aborting from %s:%d (%s: %08X, '%c%c%c%c')\n", file, line, msg, code, code & 0xFF, (code >> 8) & 0XFF, (code >> 16) & 0xFF, (code >> 24) & 0xFF); 63 | 64 | print_backtrace(); 65 | abort(); 66 | } 67 | -------------------------------------------------------------------------------- /src/dvi/dvi_fonttable.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include "mydvi.h" 28 | 29 | struct dvi_fonttable 30 | { 31 | dvi_fontdef *buffer; 32 | int capacity; 33 | }; 34 | 35 | dvi_fonttable *dvi_fonttable_new(fz_context *ctx) 36 | { 37 | dvi_fonttable *ft = fz_malloc_struct(ctx, dvi_fonttable); 38 | ft->buffer = NULL; 39 | ft->capacity = 0; 40 | return ft; 41 | } 42 | 43 | void dvi_fonttable_free(fz_context *ctx, dvi_fonttable *ft) 44 | { 45 | if (ft->buffer) 46 | fz_free(ctx, ft->buffer); 47 | fz_free(ctx, ft); 48 | } 49 | 50 | dvi_fontdef *dvi_fonttable_get(fz_context *ctx, dvi_fonttable *ft, int f) 51 | { 52 | if (f < 0 || f > 9999) 53 | { 54 | fprintf(stderr, "dvi_fonttable_get(_, %d)\n", f); 55 | abort(); 56 | } 57 | if (f >= ft->capacity) 58 | { 59 | int capacity = 2; 60 | while (capacity <= f) 61 | capacity = capacity << 1; 62 | dvi_fontdef *buffer = fz_malloc_struct_array(ctx, capacity, dvi_fontdef); 63 | if (ft->buffer) 64 | { 65 | memcpy(buffer, ft->buffer, sizeof(dvi_fontdef) * ft->capacity); 66 | fz_free(ctx, ft->buffer); 67 | } 68 | ft->capacity = capacity; 69 | ft->buffer = buffer; 70 | } 71 | return &ft->buffer[f]; 72 | } 73 | 74 | -------------------------------------------------------------------------------- /src/json_parser.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #ifndef JSON_PARSER_H_ 26 | #define JSON_PARSER_H_ 27 | 28 | #include "../dvi/vstack.h" 29 | 30 | enum json_parser_state 31 | { 32 | P_JSON_ELEMENT, 33 | P_JSON_STRING, 34 | P_JSON_STRING_ESCAPE, 35 | P_JSON_STRING_U1, 36 | P_JSON_STRING_U2, 37 | P_JSON_STRING_U3, 38 | P_JSON_STRING_U4, 39 | P_JSON_OBJECT, 40 | P_JSON_AFTER_ELEMENT, 41 | P_JSON_AFTER_NAME, 42 | P_JSON_INTEGER_SIGN, 43 | P_JSON_INTEGER_DIGITS, 44 | P_JSON_EXPONENT, 45 | P_JSON_EXPONENT_SIGN, 46 | P_JSON_EXPONENT_DIGITS, 47 | P_JSON_FRACTION, 48 | P_JSON_NULL_N, 49 | P_JSON_NULL_NU, 50 | P_JSON_NULL_NUL, 51 | P_JSON_TRUE_T, 52 | P_JSON_TRUE_TR, 53 | P_JSON_TRUE_TRU, 54 | P_JSON_FALSE_F, 55 | P_JSON_FALSE_FA, 56 | P_JSON_FALSE_FAL, 57 | P_JSON_FALSE_FALS, 58 | }; 59 | 60 | typedef struct 61 | { 62 | enum json_parser_state state; 63 | union 64 | { 65 | int codepoint; 66 | struct 67 | { 68 | int sign, exp_sign; 69 | float num, frac, exp; 70 | }; 71 | }; 72 | } json_parser; 73 | 74 | extern const json_parser initial_json_parser; 75 | 76 | const char *json_parse(fz_context *ctx, json_parser *cp, vstack *stack, const 77 | char *input, const char *limit); 78 | 79 | #endif // JSON_PARSER_H_ 80 | -------------------------------------------------------------------------------- /src/loader.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include "driver.h" 29 | 30 | char so_path[4096]; 31 | int so_ino, so_dev; 32 | 33 | static bool should_reload_binary(void) 34 | { 35 | struct stat st; 36 | if (stat(so_path, &st) != 0) 37 | { 38 | perror("stat"); 39 | fprintf(stderr, "stat(\"%s\") failed\n", so_path); 40 | return 0; 41 | } 42 | return (so_ino != st.st_ino || so_dev != st.st_dev); 43 | } 44 | 45 | bool texpresso_main(struct persistent_state *ps) 46 | { 47 | ps->should_reload_binary = &should_reload_binary; 48 | 49 | strcpy(so_path, ps->exe_path); 50 | strcat(so_path, ".so"); 51 | 52 | struct stat st; 53 | if (stat(so_path, &st) != 0) 54 | { 55 | perror("stat"); 56 | fprintf(stderr, "stat(\"%s\") failed\n", so_path); 57 | return 0; 58 | } 59 | so_ino = st.st_ino; 60 | so_dev = st.st_dev; 61 | 62 | void *handle = dlopen(so_path, RTLD_LAZY | RTLD_LOCAL); 63 | 64 | if (!handle) 65 | { 66 | perror("dlopen"); 67 | fprintf(stderr, "dlopen(\"%s\") failed\n", so_path); 68 | return 0; 69 | } 70 | 71 | bool (*main)(struct persistent_state *ps) = dlsym(handle, "texpresso_main"); 72 | 73 | if (!main) 74 | { 75 | perror("dlsym"); 76 | fprintf(stderr, "dlsym(\"texpresso_main\") failed\n"); 77 | return 0; 78 | } 79 | 80 | bool result = main(ps); 81 | dlclose(handle); 82 | 83 | return result; 84 | } 85 | -------------------------------------------------------------------------------- /src/dvi/fixed.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #ifndef FIXED_H 26 | #define FIXED_H 27 | 28 | #include 29 | 30 | #define K_PT2IN (1.0/72.27) 31 | #define K_PT2BP (K_PT2IN*72.0) 32 | #define K_PT2CM (K_PT2IN*2.54) 33 | #define K_PT2MM (K_PT2CM*10.0) 34 | #define K_PT2PC (1.0/12.0) 35 | #define K_PT2DD (1157.0/1238.0) 36 | #define K_PT2CC (K_PT2DD/12.0) 37 | #define K_PT2SP (65536.0) 38 | 39 | typedef struct { 40 | int32_t value; 41 | } fixed_t; 42 | 43 | static inline fixed_t fixed_make(int32_t repr); 44 | static inline int fixed_compare(fixed_t a, fixed_t b); 45 | static inline double fixed_double(fixed_t t); 46 | static inline fixed_t fixed_mul(fixed_t a, fixed_t b); 47 | static inline fixed_t fixed_div(fixed_t a, fixed_t b); 48 | 49 | static inline fixed_t fixed_make(int32_t repr) 50 | { 51 | return (fixed_t){.value = repr}; 52 | } 53 | 54 | static inline int fixed_compare(fixed_t a, fixed_t b) 55 | { 56 | if (a.value == b.value) 57 | return 0; 58 | else if (a.value < b.value) 59 | return -1; 60 | else 61 | return 1; 62 | } 63 | 64 | static inline double fixed_double(fixed_t t) 65 | { 66 | return (double)t.value / (1 << 20); 67 | } 68 | 69 | static inline fixed_t fixed_mul(fixed_t a, fixed_t b) 70 | { 71 | int64_t v = (int64_t)a.value * (int64_t)b.value; 72 | return fixed_make(v >> 20); 73 | } 74 | 75 | static inline fixed_t fixed_div(fixed_t a, fixed_t b) 76 | { 77 | int64_t v = ((int64_t)a.value << 20) / (int64_t)b.value; 78 | return fixed_make(v); 79 | // double d = fixed_double(a) / fixed_double(b); 80 | // return fixed_make(d * (1 << 20)); 81 | } 82 | 83 | #endif /*!FIXED*/ 84 | -------------------------------------------------------------------------------- /src/dvi/pdf_lexer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #ifndef PDF_LEXER_H_ 26 | #define PDF_LEXER_H_ 27 | 28 | #include "vstack.h" 29 | 30 | enum PDF_OP 31 | { 32 | PDF_NONE, 33 | PDF_OP_w, 34 | PDF_OP_J, 35 | PDF_OP_j, 36 | PDF_OP_M, 37 | PDF_OP_d, 38 | PDF_OP_ri, 39 | PDF_OP_i, 40 | PDF_OP_gs, 41 | PDF_OP_q, 42 | PDF_OP_Q, 43 | PDF_OP_cm, 44 | PDF_OP_m, 45 | PDF_OP_l, 46 | PDF_OP_c, 47 | PDF_OP_v, 48 | PDF_OP_y, 49 | PDF_OP_h, 50 | PDF_OP_re, 51 | PDF_OP_S, 52 | PDF_OP_s, 53 | PDF_OP_f, 54 | PDF_OP_F, 55 | PDF_OP_f_star, 56 | PDF_OP_B, 57 | PDF_OP_B_star, 58 | PDF_OP_b, 59 | PDF_OP_b_star, 60 | PDF_OP_n, 61 | PDF_OP_W, 62 | PDF_OP_W_star, 63 | PDF_OP_BT, 64 | PDF_OP_ET, 65 | PDF_OP_Tc, 66 | PDF_OP_Tw, 67 | PDF_OP_Tz, 68 | PDF_OP_TL, 69 | PDF_OP_Tf, 70 | PDF_OP_Tr, 71 | PDF_OP_Ts, 72 | PDF_OP_Td, 73 | PDF_OP_TD, 74 | PDF_OP_Tm, 75 | PDF_OP_T_star, 76 | PDF_OP_Tj, 77 | PDF_OP_TJ, 78 | PDF_OP_squote, 79 | PDF_OP_dquote, 80 | PDF_OP_d0, 81 | PDF_OP_d1, 82 | PDF_OP_CS, 83 | PDF_OP_cs, 84 | PDF_OP_SC, 85 | PDF_OP_sc, 86 | PDF_OP_SCN, 87 | PDF_OP_scn, 88 | PDF_OP_G, 89 | PDF_OP_g, 90 | PDF_OP_RG, 91 | PDF_OP_rg, 92 | PDF_OP_K, 93 | PDF_OP_k, 94 | PDF_OP_sh, 95 | PDF_OP_Do, 96 | PDF_OP_MP, 97 | PDF_OP_DP, 98 | PDF_OP_BMC, 99 | PDF_OP_BDC, 100 | PDF_OP_EMC, 101 | PDF_OP_BX, 102 | PDF_OP_EX, 103 | }; 104 | 105 | const char * 106 | pdf_op_name(enum PDF_OP op); 107 | 108 | enum PDF_OP 109 | pdf_parse_command(fz_context *ctx, vstack *t, const char **cur, const char *lim); 110 | 111 | #endif // PDF_LEXER_H_ 112 | -------------------------------------------------------------------------------- /src/editor.h: -------------------------------------------------------------------------------- 1 | #ifndef EDITOR_H_ 2 | #define EDITOR_H_ 3 | 4 | #include "driver.h" 5 | #include "vstack.h" 6 | 7 | void editor_set_protocol(enum editor_protocol protocol); 8 | void editor_set_line_output(bool line); 9 | 10 | // Receiving commands 11 | 12 | enum EDITOR_CHANGE_UNIT 13 | { 14 | CHANGE_BYTES, 15 | CHANGE_LINES, 16 | CHANGE_RANGE 17 | }; 18 | 19 | enum EDITOR_COMMAND 20 | { 21 | EDIT_OPEN, 22 | EDIT_CLOSE, 23 | EDIT_CHANGE, 24 | EDIT_THEME, 25 | EDIT_PREVIOUS_PAGE, 26 | EDIT_NEXT_PAGE, 27 | EDIT_MOVE_WINDOW, 28 | EDIT_RESCAN, 29 | EDIT_STAY_ON_TOP, 30 | EDIT_SYNCTEX_FORWARD, 31 | EDIT_MAP_WINDOW, 32 | EDIT_UNMAP_WINDOW, 33 | EDIT_CROP, 34 | EDIT_INVERT, 35 | }; 36 | 37 | struct editor_change 38 | { 39 | const char *path; 40 | const char *data; 41 | enum 42 | { 43 | BASE_BYTE, 44 | BASE_LINE, 45 | BASE_RANGE, 46 | } base; 47 | int length; 48 | union 49 | { 50 | struct 51 | { 52 | int offset, remove; 53 | } span; 54 | struct 55 | { 56 | int start_line, start_char; 57 | int end_line, end_char; 58 | } range; 59 | }; 60 | }; 61 | 62 | struct editor_command 63 | { 64 | enum EDITOR_COMMAND tag; 65 | union { 66 | struct { 67 | const char *path; 68 | const char *data; 69 | int length; 70 | } open; 71 | 72 | struct { 73 | const char *path; 74 | } close; 75 | 76 | struct editor_change change; 77 | 78 | struct { 79 | float bg[3], fg[3]; 80 | } theme; 81 | 82 | struct { 83 | } previous_page; 84 | 85 | struct { 86 | } next_page; 87 | 88 | struct { 89 | float x, y, w, h; 90 | } move_window; 91 | 92 | struct { 93 | } rescan; 94 | 95 | struct { 96 | bool status; 97 | } stay_on_top; 98 | 99 | struct { 100 | const char *path; 101 | int line; 102 | } synctex_forward; 103 | 104 | struct { 105 | float x, y, w, h; 106 | } map_window; 107 | 108 | struct { 109 | } unmap_window; 110 | 111 | struct { 112 | } crop; 113 | 114 | struct { 115 | } invert; 116 | }; 117 | }; 118 | 119 | bool editor_parse(fz_context *ctx, 120 | vstack *stack, 121 | val command, 122 | struct editor_command *out); 123 | 124 | // Sending message 125 | 126 | enum EDITOR_INFO_BUFFER 127 | { 128 | BUF_OUT, // TeX process stdout 129 | BUF_LOG, // TeX output log file 130 | }; 131 | 132 | void editor_append(enum EDITOR_INFO_BUFFER name, fz_buffer *buf, int pos); 133 | void editor_truncate(enum EDITOR_INFO_BUFFER name, fz_buffer *buf); 134 | void editor_flush(void); 135 | void editor_synctex(const char *dirname, const char *basename, int basename_len, int line, int column); 136 | void editor_reset_sync(void); 137 | void editor_notify_file_opened(int index, const char *path, int len); 138 | 139 | #endif // EDITOR_H_ 140 | -------------------------------------------------------------------------------- /src/dvi/dvi_scratch.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include "mydvi.h" 27 | 28 | struct dvi_scratch_buf 29 | { 30 | struct dvi_scratch_buf *prev; 31 | int size, cursor; 32 | uint8_t data[0]; 33 | }; 34 | 35 | void *dvi_scratch_alloc(fz_context *ctx, dvi_scratch *t, size_t len) 36 | { 37 | while (1) 38 | { 39 | // Check if there is some spare space in current buffer 40 | if (t->buf) 41 | { 42 | int cursor = t->buf->cursor - len; 43 | if (len > 3) 44 | { 45 | // Align allocation 46 | if (len > 7) 47 | cursor &= ~0x7; 48 | else if (len > 3) 49 | cursor &= ~0x3; 50 | else if (len > 1) 51 | cursor &= ~0x1; 52 | } 53 | if (cursor >= 0) 54 | { 55 | t->buf->cursor = cursor; 56 | return &t->buf->data[cursor]; 57 | } 58 | } 59 | 60 | // Allocate a new buffer 61 | size_t bufsize = t->buf ? t->buf->size * 2 : 256; 62 | while (bufsize < len) 63 | bufsize *= 2; 64 | struct dvi_scratch_buf *buf = 65 | fz_malloc(ctx, sizeof(struct dvi_scratch_buf) + bufsize); 66 | buf->prev = t->buf; 67 | buf->size = bufsize; 68 | buf->cursor = bufsize; 69 | t->buf = buf; 70 | } 71 | } 72 | 73 | static void free_bufs(fz_context *ctx, struct dvi_scratch_buf **pbuf) 74 | { 75 | struct dvi_scratch_buf *buf = *pbuf; 76 | 77 | while (buf) 78 | { 79 | struct dvi_scratch_buf *prev = buf->prev; 80 | fz_free(ctx, buf); 81 | buf = prev; 82 | } 83 | 84 | *pbuf = NULL; 85 | } 86 | 87 | void dvi_scratch_init(dvi_scratch *t) 88 | { 89 | t->buf = NULL; 90 | } 91 | 92 | void dvi_scratch_release(fz_context *ctx, dvi_scratch *t) 93 | { 94 | free_bufs(ctx, &t->buf); 95 | } 96 | 97 | void dvi_scratch_clear(fz_context *ctx, dvi_scratch *t) 98 | { 99 | if (t->buf) 100 | free_bufs(ctx, &t->buf->prev); 101 | } 102 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | # TeXpresso binaries 2 | 3 | The binary can be compiled in two ways: 4 | 5 | 1. `texpresso-dev` is a frontend that supports _hot-loading_ by dynamically linking `texpresso.so`. 6 | After updating `texpresso.so`, send a USR1 to reload it (`killall -SIGUSR1 texpresso-dev`). 7 | 8 | 2. `texpresso` is a standalone version. 9 | 10 | Produce them using `make texpresso` or `make texpresso-dev`. 11 | 12 | ## Important source files 13 | 14 | ### Entrypoints 15 | 16 | [driver.c](driver.c) implements the entrypoint (the `main` function). It is the common 17 | code between the hot-loaded and standalone version. [driver.h](driver.h) defines the 18 | interface that should be implemented by the different versions. 19 | `main` initializes the shared state and resources (mupdf and SDL2), and passes the 20 | control to `texpresso_main`. 21 | 22 | [loader.c](loader.c) implements the hot-loader, loading (and reloading) `texpresso.so` 23 | 24 | [main.c](main.c) implements the main TeXpresso interface 25 | 26 | ### Engine 27 | 28 | [engine.h](engine.h) defines the common interface between different engines. The engine 29 | is a component that takes the input file provided to `texpresso` and turns it 30 | into something that can be displayed: 31 | 32 | - [engine_dvi.c](engine_dvi.c) renders a DVI and XDV files. 33 | - [engine_pdf.c](engine_pdf.c) renders a PDF file (using MuPDF). 34 | - [engine_tex.c](engine_tex.c) renders a .tex file, by turning it into an XDV stream using 35 | texpresso-tonic (TeXpresso flavor of [tectonic](https://github.com/tectonic-typesetting/tectonic)) 36 | 37 | [dvi/](dvi/) is a generic interpreter for DVI format,with support for TeX 38 | TFM, VF, enc, PDF graphic stream, etc. 39 | 40 | [fs.c](fs.c) fakes a minimalist file-system. It keeps an in-memory copy of files 41 | read from the real file system by LaTeX (to detect changes or to patch them) and 42 | it stores files written by LaTeX. 43 | 44 | [state.c](state.c), [state.h](state.h) tracks the state of running LaTeX processes (somewhat 45 | like the unix "U structure", it keeps the list of opened file descriptors), 46 | while supporting backtracking. 47 | 48 | [incdvi.c](incdvi.c), [incdvi.h](incdvi.h) is an incremental viewer for DVI files, implemented 49 | on top of library. 50 | 51 | [renderer.c](renderer.c), [renderer.h](renderer.h) renders the contents of TeXpresso window, 52 | with support for scrolling, cropping, remapping colors, etc. 53 | 54 | ### Misc files 55 | 56 | [sexp_parser.c](sexp_parser.c), [sexp_parser.h](sexp_parser.h) is a simple S-expression parser, compatible 57 | enough with Emacs. 58 | 59 | [sprotocol.c](sprotocol.c), [sprotocol.h](sprotocol.h) is an implementation of the protocol used by 60 | TeXpresso to communicate with TeXpresso-enabled LaTeX processes. 61 | 62 | [myabort.c](myabort.c), [myabort.h](myabort.h) is an helper to print backtraces before aborting. 63 | 64 | [proxy.c](proxy.c) is a small C tool (compiled using `make texpresso-debug-proxy`) to 65 | proxy TeXpresso communication from the editor to an instance running through a 66 | debugger (launched using <../scripts/texpresso-debug>). 67 | 68 | [synctex.c](synctex.c), [synctex.h](synctex.h) is a quick'n'dirty SyncTeX parser (not used in 69 | current version). 70 | 71 | [logo.c](logo.c), [logo.h](logo.h) is the TeXpresso logo, represented as a [qoi.h](qoi.h) image 72 | and serialized as a C string. 73 | -------------------------------------------------------------------------------- /src/state.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #ifndef STATE_H 26 | #define STATE_H 27 | 28 | #include 29 | #include 30 | #include "sprotocol.h" 31 | 32 | #define MAX_FILES 1024 33 | 34 | enum accesslevel { 35 | FILE_NONE, 36 | FILE_READ, 37 | FILE_WRITE 38 | }; 39 | 40 | typedef int mark_t; 41 | 42 | typedef struct fileentry_s { 43 | const char *path; 44 | 45 | // Cache of filesystem state 46 | struct stat fs_stat; 47 | fz_buffer *fs_data; 48 | 49 | // Cached picture information 50 | struct pic_cache pic_cache; 51 | 52 | // State of the file in the text editor (or NULL if unedited) 53 | fz_buffer *edit_data; 54 | 55 | // State observed and/or produced by TeX process 56 | struct { 57 | fz_buffer *data; 58 | enum accesslevel level; 59 | mark_t snap; 60 | } saved; 61 | 62 | int seen; 63 | int debug_rollback_invalidation; 64 | } fileentry_t; 65 | 66 | typedef struct filecell_s { 67 | mark_t snap; 68 | fileentry_t *entry; 69 | } filecell_t; 70 | 71 | typedef struct { 72 | filecell_t table[MAX_FILES]; 73 | filecell_t stdout, document, synctex, log; 74 | } state_t; 75 | 76 | void state_init(state_t *st); 77 | 78 | typedef struct filesystem_s filesystem_t; 79 | typedef struct log_s log_t; 80 | 81 | filesystem_t *filesystem_new(fz_context *ctx); 82 | void filesystem_free(fz_context *ctx, filesystem_t *fs); 83 | fileentry_t *filesystem_lookup_or_create(fz_context *ctx, filesystem_t *fs, const char *path); 84 | fileentry_t *filesystem_lookup(filesystem_t *fs, const char *path); 85 | fileentry_t *filesystem_scan(filesystem_t *fs, int *index); 86 | 87 | log_t *log_new(fz_context *ctx); 88 | void log_free(fz_context *ctx, log_t *log); 89 | mark_t log_snapshot(fz_context *ctx, log_t *log); 90 | void log_rollback(fz_context *ctx, log_t *log, mark_t snapshot); 91 | void log_fileentry(fz_context *ctx, log_t *log, fileentry_t *entry); 92 | void log_filecell(fz_context *ctx, log_t *log, filecell_t *cell); 93 | void log_overwrite(fz_context *ctx, log_t *log, fz_buffer *buf, int start, int len); 94 | 95 | bool stat_same(struct stat *st1, struct stat *st2); 96 | 97 | #endif /*!STATE_H*/ 98 | -------------------------------------------------------------------------------- /src/renderer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #ifndef _RENDERER_H_ 26 | #define _RENDERER_H_ 27 | 28 | #include 29 | #include 30 | #include 31 | 32 | typedef struct txp_renderer_s txp_renderer; 33 | 34 | txp_renderer *txp_renderer_new(fz_context *ctx, SDL_Renderer *sdl); 35 | void txp_renderer_free(fz_context *ctx, txp_renderer *r); 36 | 37 | enum txp_fit_mode 38 | { 39 | FIT_WIDTH, 40 | FIT_PAGE, 41 | }; 42 | 43 | typedef struct 44 | { 45 | float zoom; 46 | enum txp_fit_mode fit; 47 | fz_point pan; 48 | bool crop, themed_color, invert_color; 49 | uint32_t background_color, foreground_color; 50 | } txp_renderer_config; 51 | 52 | typedef struct 53 | { 54 | // Bounds of the page being displayed (after cropping), in 55 | // document space. 56 | fz_rect page_bounds; 57 | 58 | // Size of the window where the document is displayed. 59 | fz_point window_size; 60 | 61 | // Size of the page being displayed, in window space. 62 | fz_point document_size; 63 | 64 | // Panning range from -pan_interval to pan_interval. 65 | fz_point pan_interval; 66 | } txp_renderer_bounds; 67 | 68 | void txp_renderer_set_contents(fz_context *ctx, txp_renderer *self, fz_display_list *dl); 69 | fz_display_list *txp_renderer_get_contents(fz_context *ctx, txp_renderer *self); 70 | txp_renderer_config *txp_renderer_get_config(fz_context* ctx, txp_renderer *self); 71 | 72 | bool txp_renderer_page_bounds(fz_context *ctx, txp_renderer *self, txp_renderer_bounds *result); 73 | bool txp_renderer_page_position(fz_context *ctx, txp_renderer *self, SDL_FRect *rect, fz_point *translate, float *scale); 74 | 75 | void txp_renderer_render(fz_context *ctx, txp_renderer *self); 76 | void txp_renderer_set_scale_factor(fz_context *ctx, txp_renderer *self, fz_point scale); 77 | bool txp_renderer_start_selection(fz_context *ctx, txp_renderer *self, fz_point pt); 78 | bool txp_renderer_drag_selection(fz_context *ctx, txp_renderer *self, fz_point pt); 79 | bool txp_renderer_select_word(fz_context *ctx, txp_renderer *self, fz_point pt); 80 | bool txp_renderer_select_char(fz_context *ctx, txp_renderer *self, fz_point pt); 81 | void txp_renderer_screen_size(fz_context *ctx, txp_renderer *self, int *w, int *h); 82 | fz_point txp_renderer_screen_to_document(fz_context *ctx, txp_renderer *self, fz_point pt); 83 | fz_point txp_renderer_document_to_screen(fz_context *ctx, txp_renderer *self, fz_point pt); 84 | 85 | #endif /*!_RENDERER_H_*/ 86 | -------------------------------------------------------------------------------- /src/utf_mapping.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | // Move the pointer `start` to an UTF-8 encoded string ending at `end` forward 7 | // by `count` UTF-16 code units. 8 | // Return NULL if a fatal error was encountered (invalid encoding or `count` 9 | // points in the middle of a surrogate pair). 10 | // LSP ranges 11 | static const void *move_utf8_by_utf16_codeunits(const void *start, 12 | const void *end, 13 | int count) 14 | { 15 | if (start >= end) 16 | { 17 | if (count == 0) 18 | return start; 19 | return NULL; 20 | } 21 | 22 | const unsigned char *p = start; 23 | int warned_continuation = 0; 24 | 25 | while (count > 0) 26 | { 27 | unsigned char b = *p; 28 | 29 | // 1-byte UTF-8: ASCII char 30 | if (b < 0x80) 31 | { 32 | 33 | // Error: reached the end of the line 34 | if (b == '\n') 35 | { 36 | fprintf(stderr, 37 | "[error] Invalid UTF-16 range: " 38 | "pointing past end of line\n"); 39 | return NULL; 40 | } 41 | 42 | // Count as single UTF-16 code unit 43 | count -= 1; 44 | p += 1; 45 | 46 | // Out of bounds 47 | if ((void*)p > end) 48 | return NULL; 49 | } 50 | 51 | // 1-byte: continuation (warn and ignore) 52 | else if (b < 0xC0) 53 | { 54 | if (!warned_continuation) 55 | { 56 | fprintf(stderr, 57 | "[warning] UTF-16 range: " 58 | "unexpected continuation byte\n"); 59 | warned_continuation = 1; 60 | } 61 | p += 1; 62 | } 63 | 64 | // 2-byte UTF-8, 1 UTF-16 code unit 65 | else if (b < 0xE0) 66 | { 67 | count -= 1; 68 | p += 2; 69 | if ((void*)p <= end && p[-1] == '\n') 70 | goto invalid_nl; 71 | } 72 | 73 | // 3-byte UTF-8, 1 UTF-16 code unit 74 | else if (b < 0xF0) 75 | { 76 | count -= 1; 77 | p += 3; 78 | if ((void*)p <= end && (p[-1] == '\n' || p[-2] == '\n')) 79 | goto invalid_nl; 80 | } 81 | 82 | // 4-byte UTF-8 encodings: translate to a surrogate pair, needing two 83 | // UTF-16 code units 84 | else 85 | { 86 | p += 4; 87 | count -= 2; 88 | if ((void*)p <= end && (p[-1] == '\n' || p[-2] == '\n' || p[-3] == '\n')) 89 | goto invalid_nl; 90 | } 91 | } 92 | 93 | // At this point, count is: 94 | // - exactly 0 if the traversal succeeded 95 | // - (-1) if the offset given pointed in the middle of a surrogate pair 96 | // - >0 if the offset is pointing past the end of the buffer 97 | // 98 | if (count > 0 || (void*)p > end) 99 | { 100 | fprintf(stderr, 101 | "[error] Invalid UTF-16 range: " 102 | "pointing past end of buffer\n"); 103 | return NULL; 104 | } 105 | 106 | if (count < 0) 107 | { 108 | fprintf(stderr, 109 | "[error] Invalid UTF-16 range: " 110 | "pointing in the middle of a surrogate pair\n"); 111 | return NULL; 112 | } 113 | 114 | return p; 115 | 116 | invalid_nl: 117 | fprintf(stderr, 118 | "[error] Broken UTF-8 encoding: " 119 | "line return in the middle of a codepoint\n"); 120 | return NULL; 121 | } 122 | 123 | static int utf16_to_utf8_offset(const void *p, const void *end, size_t utf16_index) 124 | { 125 | const void *p2 = move_utf8_by_utf16_codeunits(p, end, utf16_index); 126 | 127 | if (p2 && p2 <= end) 128 | return p2 - p; 129 | else 130 | return -1; 131 | } 132 | -------------------------------------------------------------------------------- /test/test_utf_mapping.c: -------------------------------------------------------------------------------- 1 | #include "../src/utf_mapping.h" 2 | 3 | // Test strings covering all corner cases 4 | struct test_vec { 5 | const char *name, *comment, *input; 6 | } tests[] = { 7 | {"test_ascii", "Basic ASCII (1-byte UTF-8, 1 UTF-16 code unit)", "Hello\n"}, 8 | 9 | {"test_2byte", 10 | "Single 2-byte UTF-8 (é) - valid, should advance 1 UTF-16 unit", 11 | "caf\xc3\xa9\n"}, 12 | 13 | {"test_3byte", 14 | "Single 3-byte UTF-8 (€) - valid, should advance 1 UTF-16 unit", 15 | "caf\xe2\x82\xac\n"}, 16 | 17 | {"test_4byte", 18 | "Single 4-byte UTF-8 (🌈) - valid, should advance 2 UTF-16 units", 19 | "caf\xf0\x9f\x8c\x88\n"}, 20 | 21 | {"test_mixed", "Mixed: 1-byte + 2-byte + 3-byte + 4-byte", 22 | "A\xc3\xa9\xe2\x82\xac\xf0\x9f\x8c\x88\n"}, 23 | 24 | {"test_invalid_continuation", "Invalid continuation byte (no leading byte)", 25 | "\x80\x81\x82\n"}, 26 | 27 | {"test_overlong", "Overlong encoding (e.g., 2-byte for U+0000)", 28 | "\xC0\x80\xC1\xBF\n"}, 29 | 30 | {"test_surrogate", 31 | "Surrogate pair in UTF-8 (invalid, should trigger error, U+D800)", 32 | "\xED\xA0\x80\n"}, 33 | 34 | {"test_surrogate_pair", "Surrogate pair (U+D800 + U+DFFF)", 35 | "\xED\xA0\x80\xED\xAF\xBF\n"}, 36 | 37 | {"test_nl_in_middle_2byte", 38 | "Line feed in the middle of a codepoint (should fail)", 39 | "ca\xE9\n"}, // \xE9 is continuation, but \xE9 alone is invalid 40 | 41 | {"test_nl_in_middle_3byte", "\\xE2\\x82 is incomplete 3-byte", 42 | "ca\xE2\x82\n"}, 43 | 44 | {"test_nl_in_middle_4byte", "incomplete 4-byte", "ca\xF0\x90\x8C\n"}, 45 | 46 | {"test_nl_after_surrogate", 47 | "Line feed after partial surrogate pair (high surrogate, then \\n)", 48 | "\xED\xA0\n"}, 49 | 50 | {"test_trailing_continuation", 51 | "Trailing continuation bytes (no leading byte)", "\x80\x81\x82\x83\n"}, 52 | 53 | {"test_end_mid_4byte", "incomplete 4-byte", "caf\xc3\xa9\xF0\x90\x8C"}, 54 | 55 | {"test_middle_surrogate", 56 | "Pointing in middle of surrogate pair " 57 | "(should fail, U+D800 not followed by low surrogate)", 58 | "\xED\xA0\x80"}, 59 | 60 | {NULL, NULL, NULL}, 61 | }; 62 | 63 | int fails = 0; 64 | int last_offset = 0; 65 | 66 | static void output_marker_at_offset(int offset) 67 | { 68 | if (offset <= last_offset) 69 | abort(); 70 | 71 | while (offset > last_offset && fails > 0) 72 | { 73 | putchar('_'); 74 | last_offset += 1; 75 | fails -= 1; 76 | } 77 | fails = 0; 78 | 79 | while (offset > last_offset) 80 | { 81 | putchar(' '); 82 | last_offset += 1; 83 | } 84 | 85 | putchar('^'); 86 | last_offset += 1; 87 | } 88 | 89 | int main() 90 | { 91 | int counter = 0; 92 | for (struct test_vec *test = tests; test->name; test++) 93 | { 94 | printf("# Test %d. %s: %s\n\n", ++counter, test->name, test->comment); 95 | 96 | int len = strlen(test->input); 97 | const unsigned char *input = (void*)test->input; 98 | for (int i = 0; i < len; i++) 99 | { 100 | if (input[i] < 0x80 && input[i] > ' ') 101 | printf("| %c ", input[i]); 102 | else 103 | printf("| %02X ", input[i]); 104 | } 105 | printf("|\n"); 106 | 107 | fails = 0; 108 | last_offset = 0; 109 | 110 | for (int i = 0; i < len; i++) 111 | { 112 | int count = utf16_to_utf8_offset(test->input, test->input + len, i); 113 | if (count == -1) 114 | fails += 1; 115 | else 116 | output_marker_at_offset(count * 5 + 2); 117 | } 118 | 119 | while (fails > 0) 120 | { 121 | putchar('_'); 122 | fails -= 1; 123 | } 124 | printf("\n"); 125 | fflush(NULL); 126 | printf("\n"); 127 | } 128 | 129 | return 0; 130 | } 131 | -------------------------------------------------------------------------------- /src/dvi/mydvi_interp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #ifndef DVI_INTERP_H 26 | #define DVI_INTERP_H 27 | 28 | #include "mydvi.h" 29 | 30 | // DVI interpreter 31 | 32 | const char *dvi_opname(uint8_t op); 33 | 34 | int dvi_preamble_size(const uint8_t *buf, int len); 35 | bool dvi_preamble_parse(fz_context *ctx, dvi_context *dc, dvi_state *st, const uint8_t *buf); 36 | 37 | int dvi_instr_size(const uint8_t *buf, int len, enum dvi_version version); 38 | bool dvi_interp_sub(fz_context *ctx, dvi_context *dc, dvi_state *st, const uint8_t *buf); 39 | bool dvi_interp(fz_context *ctx, dvi_context *dc, const uint8_t *buf); 40 | void dvi_interp_init(fz_context *ctx, dvi_context *dc, const uint8_t *bop, int len); 41 | int dvi_interp_bop(const uint8_t *bop, int len, float *width, float *height, bool *landscape); 42 | 43 | // DVI primitives 44 | 45 | double dvi_scale(dvi_state *st, fixed_t dim); 46 | void dvi_exec_char(fz_context *ctx, dvi_context *dc, dvi_state *st, uint32_t c, bool set); 47 | bool dvi_exec_push(fz_context *ctx, dvi_context *dc, dvi_state *st); 48 | bool dvi_exec_pop(fz_context *ctx, dvi_context *dc, dvi_state *st); 49 | void dvi_exec_fnt_num(fz_context *ctx, dvi_context *dc, dvi_state *st, uint32_t f); 50 | void dvi_exec_rule(fz_context *ctx, dvi_context *dc, dvi_state *st, uint32_t w, uint32_t h); 51 | bool dvi_exec_fnt_def(fz_context *ctx, dvi_context *dc, dvi_state *st, 52 | uint32_t f, uint32_t c, uint32_t s, uint32_t d, 53 | const char *path, size_t pathlen, const char *name, size_t namelen); 54 | bool dvi_exec_bop(fz_context *ctx, dvi_context *dc, dvi_state *st, uint32_t c[10], uint32_t p); 55 | void dvi_exec_eop(fz_context *ctx, dvi_context *dc, dvi_state *st); 56 | bool dvi_exec_pre(fz_context *ctx, dvi_context *dc, dvi_state *st, 57 | uint8_t i, uint32_t num, uint32_t den, uint32_t mag, 58 | const char *comment, size_t len); 59 | void dvi_exec_xdvfontdef(fz_context *ctx, dvi_context *dc, dvi_state *st, uint32_t fontnum, 60 | const char *filename, int filename_len, int index, dvi_xdvfontspec spec); 61 | void dvi_exec_xdvglyphs(fz_context *ctx, dvi_context *dc, dvi_state *st, fixed_t width, 62 | int char_count, uint16_t *chars, 63 | int num_glyphs, fixed_t *dx, fixed_t dy0, fixed_t *dy, uint16_t *glyphs); 64 | 65 | // Specials 66 | 67 | bool dvi_exec_special(fz_context *ctx, dvi_context *dc, dvi_state *st, const char *ptr, const char *lim); 68 | bool dvi_init_special(fz_context *ctx, dvi_context *dc, dvi_state *st, const char *ptr, const char *lim); 69 | void dvi_prescan_special(const char *ptr, const char *lim, float *width, float *height, bool *landscape); 70 | 71 | #endif /*!DVI_INTERP_H*/ 72 | -------------------------------------------------------------------------------- /SERVER-PROTOCOL.md: -------------------------------------------------------------------------------- 1 | # Client-server protocol 2 | 3 | The server (TeXpresso) coordinates clients (Tectonic/TeX) to render the document. The server has control over all processes, but it is the clients that do the actual work. 4 | 5 | The many "clients" are successive forks of a root process. 6 | 7 | The server spawns this root client and use a fresh unix domain socket for bidirectional communication. 8 | 9 | Implementations: 10 | - C server in files [sprotocol.h](src/sprotocol.h) and [sprotocol.c](src/sprotocol.c) 11 | - Rust client in [texpresso\_protocol](tectonic/crates/texpresso_protocol/src/lib.rs) crate 12 | 13 | ## Communication 14 | 15 | First, the client writes `TEXPRESSOC01` and the server writes `TEXPRESSOS01`. 16 | 17 | Then the server reads `TEXPRESSOC01` and the client reads `TEXPRESSOS01`. If any of these fails, the client aborts or is killed by the server. 18 | 19 | After that, the protocol is a serie of questions and answers mostly driven by the client with occasional requests from the server. 20 | 21 | Questions and answers starts with a four letter identifier. 22 | 23 | Client queries: 24 | 25 | - `OPEN(PATH: TEXT, MODE: TEXT, ID: FILEID) -> DONE | PASS` 26 | 27 | Client wants open a file for reading or writing. 28 | 29 | - `READ(FILE: FILEID, SIZE: INT) -> READ(BUF: BYTES) | FORK` 30 | 31 | Client wants to read from a file that has been opened for reading (if not it 32 | is an offense worth killing). The server can either satisfy the read 33 | (potentially with less bytes than requested) or ask the client to fork. 34 | The bytes that are sent to the client are not yet considered as "observed" by 35 | the server. The server considers that a byte has been observed only when the 36 | client acknowledge with a `SEEN` message (see below). 37 | This is necessary to buffer contents (avoiding some communication overhead) 38 | while enabling fine-grained tracking of the process state. 39 | 40 | - `WRIT(FILE: FILEID, BUF: BYTES) -> DONE` 41 | 42 | Client wants to write to a file opened from writing. 43 | 44 | - `CLOS(FILE: FILEID) -> DONE` 45 | 46 | Clients no longer need the give file, it can be closed. 47 | 48 | - `SEEK(FILE: FILEID, POS: INT) -> DONE` 49 | 50 | Clients want to seek to a given position in a file (opened for reading or writing). 51 | 52 | - `SEEN(FILE: FILEID, POS: INT)` 53 | 54 | Notify the server that the content up to the given position has been observed by the process. 55 | There is no answer, it is just a notification to the server. 56 | This is the only way for the server to know that a client has observed some contents. 57 | 58 | - `CHLD(PID: INT; AUXILIARY FD) -> DONE` 59 | The client forked, the argument is the pid of the new child. 60 | The file descriptor to use to communicate with the new child is 61 | 62 | - `SPIC(PATH: TEXT, TYPE: INT, PAGE: INT, BOUNDS: FLOAT[4]) -> DONE` 63 | "Store pic [boundaries]". For performance reason, this is used to cache the 64 | dimension of a picture included in a LaTeX document. 65 | The serveur maintains a hashtable storing boundaries, expressed as 4 floats, 66 | and indexed by path, type and page. 67 | This infrastructure often allow to skip rescanning JPG/PNG/PDF, significantly increasing performance during incremental changes. FIXME: only path is used as an index, the pair (type, page) is stored in a single cell cache 68 | 69 | - `GPIC(PATH: TEXT, TYPE: INT, PAGE: INT) -> GPIC(BOUNDS: FLOAT[4]) | PASS` 70 | "Get pic [boundaries]". If the file as path has not changed and is queried for a type and page that was previously stored using `SPIC`, the previous boundaries should be returned; it is always safe to `PASS`, but that can affect the performance. 71 | 72 | 73 | Server queries: 74 | 75 | - `FLSH` 76 | The client should flush (invalidate) all its read buffers; their content might be out of date. No answer is expected. 77 | 78 | Whenever the client wants to send a query or read an answer, it should first check for any outstanding query from the server. 79 | 80 | When the server no longer needs a client, it is simply killed by sending a TERM signal. 81 | -------------------------------------------------------------------------------- /doc/pres/pres.tex: -------------------------------------------------------------------------------- 1 | \documentclass{beamer} 2 | \usepackage[english]{babel} 3 | \usepackage[utf8x]{inputenc} 4 | \usepackage{graphicx} 5 | \usepackage{tikz} 6 | 7 | \usetheme{default} 8 | \usecolortheme{default} 9 | \usefonttheme{default} 10 | \setbeamertemplate{caption} 11 | 12 | \title{TeXpresso: live \LaTeX{} rendering} 13 | \author{@let-def} 14 | \date{\today} 15 | 16 | \begin{document} 17 | 18 | \begin{frame} 19 | \titlepage 20 | \end{frame} 21 | 22 | \begin{frame}{TeXpresso workflow} 23 | \begin{itemize} 24 | \item TeXpresso is a tool for previewing \LaTeX{} documents live. 25 | \item It supports Linux and macOS. \\ 26 | {\small $\rightarrow$ Snapshotting is done using {\em fork(2)}.} 27 | \item It comes with an Emacs mode for live synchronisation. \\ 28 | {\small $\rightarrow$ Alternatively, it detects changed files on SIGUSR1. \\ E.g. use \texttt{:!killall -SIGUSR1 texpresso} from vim.} 29 | \item It also reports \underline{errors and \hskip 0em warnings!} 30 | \end{itemize} 31 | \end{frame} 32 | 33 | \begin{frame}{Compatibility} 34 | \begin{itemize} 35 | \item Typesetting is done using a customized Tectonic \& XeTeX 36 | \item Rendering uses a custom DVI interpreter using MuPDF. \\ 37 | Not perfect but quite compatible; e.g TikZ: \\ 38 | \begin{tikzpicture} 39 | \draw[gray, thick] (1,1) -- (-1,-1) -- (1,-1) -- (1,1); 40 | \filldraw[black] (1,0) node[anchor=west]{This is a triangle !}; 41 | \end{tikzpicture} 42 | \item and Graphics: \includegraphics[scale=0.2]{../texpresso_logo.png} 43 | \item However, shell-escape is disabled! \\ (no live rendering of minted) :-( 44 | \end{itemize} 45 | \end{frame} 46 | 47 | \begin{frame}{Coming soon...} 48 | \begin{itemize} 49 | \item TeXpresso is slowly developed on my free time. \\ 50 | \item I hope to have an alpha version, MIT-licensed, soon. 51 | \item Bonus: \textbf{dark mode} to take care of your fragile eyes \\ 52 | \ldots and optional integration with \textbf{Emacs themes}. 53 | \end{itemize} 54 | \end{frame} 55 | 56 | \begin{frame}{That's all!} 57 | Thanks: 58 | \begin{itemize} 59 | \item to the developers of all free software that made this possible 60 | \item to the artists of the public-domain assets used in the logo! 61 | \item and to you for watching :-) 62 | \end{itemize} 63 | \end{frame} 64 | 65 | % \begin{frame}{Live edition} 66 | % TeXpresso is able to synchronize Emacs buffers in live. 67 | % 68 | % This is the preferred workflow. 69 | % 70 | % For other editors, it can efficiently reload files from the file system when receiving a SIGUSR1 signal. 71 | % \end{frame} 72 | % 73 | % \begin{frame}{Live error report} 74 | % If you use Emacs, it is also able to report LaTeX error warnings in real-time. 75 | % Not a silver bullet, but convenient for debugging. 76 | % ThisIsNotAValid Command. Better :) 77 | % 78 | % There is also a limited support for SyncTeX synchronization (but Beamer is not the best illustration of this feature!) 79 | % \end{frame} 80 | % 81 | % \begin{frame}{Eye candy} 82 | % The renderer can use a dark mode, ... Or import an Emacs theme. 83 | % Not bad :). 84 | % \end{frame} 85 | % \section{Section Two} 86 | % 87 | % \begin{frame}{Slide with table} 88 | % \input{tables/table1.tex} 89 | % \end{frame} 90 | % 91 | % \begin{frame}{Slide with figure} 92 | % \begin{figure}[H] 93 | % \centering 94 | % \includegraphics[width=.5\textwidth]{figures/figure1.png} 95 | % \caption{Caption for figure one.} 96 | % \label{fig:figure1} 97 | % \end{figure} 98 | % \end{frame} 99 | % 100 | % \begin{frame}{Slide with references} 101 | % This is to reference a figure (Figure \ref{fig:figure1})\\ 102 | % This it to reference a table (Table \ref{tab:table1})\\ 103 | % This is to cite an article \cite{Ahmed2018a}\\ 104 | % This is to add an article to the references without mentioning in the text \nocite{Ahmed2018a}\\ 105 | % \end{frame} 106 | % \section{References} 107 | % 108 | % % Adding the option 'allowframebreaks' allows the contents of the slide to be expanded in more than one slide. 109 | % \begin{frame}[allowframebreaks]{References} 110 | % \tiny\bibliography{references} 111 | % \bibliographystyle{apalike} 112 | % \end{frame} 113 | 114 | \end{document} 115 | -------------------------------------------------------------------------------- /src/engine_pdf.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include "engine.h" 28 | 29 | struct pdf_engine 30 | { 31 | struct txp_engine_class *_class; 32 | char *path; 33 | int page_count; 34 | fz_document *doc; 35 | bool changed; 36 | }; 37 | 38 | #define SELF struct pdf_engine *self = (struct pdf_engine*)_self 39 | 40 | // Useful routines 41 | 42 | TXP_ENGINE_DEF_CLASS; 43 | 44 | static void engine_destroy(txp_engine *_self, fz_context *ctx) 45 | { 46 | SELF; 47 | fz_free(ctx, self->path); 48 | fz_drop_document(ctx, self->doc); 49 | } 50 | 51 | static fz_display_list *engine_render_page(txp_engine *_self, 52 | fz_context *ctx, 53 | int index) 54 | { 55 | SELF; 56 | fz_page *page = fz_load_page(ctx, self->doc, index); 57 | fz_display_list *dl = fz_new_display_list_from_page(ctx, page); 58 | fz_drop_page(ctx, page); 59 | return dl; 60 | } 61 | 62 | static bool engine_step(txp_engine *_self, 63 | fz_context *ctx, 64 | bool restart_if_needed) 65 | { 66 | return 0; 67 | } 68 | 69 | static void engine_begin_changes(txp_engine *_self, fz_context *ctx) 70 | { 71 | } 72 | 73 | static void engine_detect_changes(txp_engine *_self, fz_context *ctx) 74 | { 75 | SELF; 76 | fz_document *doc = fz_open_document(ctx, self->path); 77 | if (!doc) 78 | return; 79 | 80 | fz_drop_document(ctx, self->doc); 81 | self->doc = doc; 82 | self->page_count = fz_count_pages(ctx, doc); 83 | self->changed = 1; 84 | } 85 | 86 | static bool engine_end_changes(txp_engine *_self, fz_context *ctx) 87 | { 88 | SELF; 89 | if (self->changed) 90 | { 91 | self->changed = 0; 92 | return 1; 93 | } 94 | else 95 | return 0; 96 | } 97 | 98 | static int engine_page_count(txp_engine *_self) 99 | { 100 | SELF; 101 | return self->page_count; 102 | } 103 | 104 | static txp_engine_status engine_get_status(txp_engine *_self) 105 | { 106 | return DOC_TERMINATED; 107 | } 108 | 109 | static float engine_scale_factor(txp_engine *_self) 110 | { 111 | return 1; 112 | } 113 | 114 | static synctex_t *engine_synctex(txp_engine *_self, fz_buffer **buf) 115 | { 116 | return NULL; 117 | } 118 | 119 | static fileentry_t *engine_find_file(txp_engine *_self, fz_context *ctx, const char *path) 120 | { 121 | return NULL; 122 | } 123 | 124 | static void engine_notify_file_changes(txp_engine *_self, 125 | fz_context *ctx, 126 | fileentry_t *entry, 127 | int offset) 128 | { 129 | } 130 | 131 | txp_engine *txp_create_pdf_engine(fz_context *ctx, const char *pdf_path) 132 | { 133 | fz_document *doc = fz_open_document(ctx, pdf_path); 134 | if (!doc) 135 | return NULL; 136 | 137 | struct pdf_engine *self = fz_malloc_struct(ctx, struct pdf_engine); 138 | self->_class = &_class; 139 | 140 | self->path = fz_strdup(ctx, pdf_path); 141 | self->doc = doc; 142 | self->page_count = fz_count_pages(ctx, doc); 143 | 144 | return (txp_engine*)self; 145 | } 146 | -------------------------------------------------------------------------------- /src/engine_dvi.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include "engine.h" 28 | #include "incdvi.h" 29 | 30 | struct dvi_engine 31 | { 32 | struct txp_engine_class *_class; 33 | fz_buffer *buffer; 34 | incdvi_t *dvi; 35 | }; 36 | 37 | #define SELF struct dvi_engine *self = (struct dvi_engine*)_self 38 | 39 | // Useful routines 40 | 41 | TXP_ENGINE_DEF_CLASS; 42 | 43 | static void engine_destroy(txp_engine *_self, fz_context *ctx) 44 | { 45 | SELF; 46 | fz_drop_buffer(ctx, self->buffer); 47 | incdvi_free(ctx, self->dvi); 48 | } 49 | 50 | static fz_display_list *engine_render_page(txp_engine *_self, 51 | fz_context *ctx, 52 | int index) 53 | { 54 | SELF; 55 | float width, height; 56 | incdvi_page_dim(self->dvi, self->buffer, index, &width, &height, NULL); 57 | fz_rect box = fz_make_rect(0, 0, width, height); 58 | fz_display_list *dl = fz_new_display_list(ctx, box); 59 | fz_device *dev = fz_new_list_device(ctx, dl); 60 | incdvi_render_page(ctx, self->dvi, self->buffer, index, dev); 61 | fz_close_device(ctx, dev); 62 | fz_drop_device(ctx, dev); 63 | return dl; 64 | } 65 | 66 | static bool engine_step(txp_engine *_self, 67 | fz_context *ctx, 68 | bool restart_if_needed) 69 | { 70 | return 0; 71 | } 72 | 73 | static void engine_begin_changes(txp_engine *_self, fz_context *ctx) 74 | { 75 | } 76 | 77 | static void engine_detect_changes(txp_engine *_self, fz_context *ctx) 78 | { 79 | } 80 | 81 | static bool engine_end_changes(txp_engine *_self, fz_context *ctx) 82 | { 83 | return 0; 84 | } 85 | 86 | 87 | static int engine_page_count(txp_engine *_self) 88 | { 89 | SELF; 90 | return incdvi_page_count(self->dvi); 91 | } 92 | 93 | static txp_engine_status engine_get_status(txp_engine *_self) 94 | { 95 | return DOC_TERMINATED; 96 | } 97 | 98 | static float engine_scale_factor(txp_engine *_self) 99 | { 100 | SELF; 101 | return incdvi_tex_scale_factor(self->dvi); 102 | } 103 | 104 | static synctex_t *engine_synctex(txp_engine *_self, fz_buffer **buf) 105 | { 106 | return NULL; 107 | } 108 | 109 | static fileentry_t *engine_find_file(txp_engine *_self, fz_context *ctx, const char *path) 110 | { 111 | return NULL; 112 | } 113 | 114 | static void engine_notify_file_changes(txp_engine *_self, 115 | fz_context *ctx, 116 | fileentry_t *entry, 117 | int offset) 118 | { 119 | } 120 | 121 | txp_engine *txp_create_dvi_engine(fz_context *ctx, const char *tectonic_path, const char *dvi_dir, const char *dvi_path) 122 | { 123 | fz_buffer *buffer = fz_read_file(ctx, dvi_path); 124 | struct dvi_engine *self = fz_malloc_struct(ctx, struct dvi_engine); 125 | self->_class = &_class; 126 | self->buffer = buffer; 127 | bundle_server *bundle = bundle_server_start(ctx, tectonic_path, dvi_dir); 128 | self->dvi = incdvi_new(ctx, bundle_server_hooks(bundle)); 129 | incdvi_update(ctx, self->dvi, buffer); 130 | return (txp_engine*)self; 131 | } 132 | -------------------------------------------------------------------------------- /src/dvi/tex_enc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include "mydvi.h" 27 | #include "fz_util.h" 28 | 29 | struct tex_enc { 30 | fz_buffer *buffer; 31 | const char *name; 32 | const char *entries[256]; 33 | }; 34 | 35 | tex_enc *tex_enc_load(fz_context *ctx, fz_stream *stream) 36 | { 37 | fz_ptr(tex_enc, result); 38 | fz_ptr(fz_buffer, buffer); 39 | 40 | fz_try(ctx) 41 | { 42 | buffer = fz_read_all(ctx, stream, 4096); 43 | fz_resize_buffer(ctx, buffer, buffer->len + 1); 44 | result = fz_malloc_struct(ctx, tex_enc); 45 | result->buffer = buffer; 46 | 47 | #define is_nl(c) ((c) == '\n') 48 | 49 | #define is_delim(c) \ 50 | (c) == '/' || (c) == '%' || (c) == '[' || (c) == ']' 51 | 52 | #define is_delim_or_ws(c) \ 53 | is_delim(c) || (c) == ' ' || (c) == '\t' 54 | 55 | #define seek(ptr, pred) \ 56 | while (*(ptr) && !is_nl(*(ptr)) && !(pred(*(ptr)))) (ptr)++ 57 | 58 | char *ptr = (char *)buffer->data; 59 | char *ending = ptr + buffer->len - 1; 60 | int entry = -1; 61 | 62 | while (*ptr) 63 | { 64 | seek(ptr, is_delim); 65 | if (*ptr == '%') 66 | { 67 | //fprintf(stderr, "skipping comment\n"); 68 | seek(ptr, is_nl); 69 | if (*ptr) ptr++; 70 | continue; 71 | } 72 | if (*ptr == '[') 73 | { 74 | //fprintf(stderr, "beginning entries\n"); 75 | entry = 0; 76 | ptr++; 77 | continue; 78 | } 79 | if (*ptr == ']') 80 | { 81 | //fprintf(stderr, "finishing entries\n"); 82 | break; 83 | } 84 | if (*ptr == '\n') 85 | { 86 | ptr++; 87 | continue; 88 | } 89 | if (*ptr == '/') 90 | { 91 | //fprintf(stderr, "parsing name\n"); 92 | ptr++; 93 | const char *name = ptr; 94 | seek(ptr, is_delim_or_ws); 95 | *ending = '\0'; 96 | ending = ptr; 97 | 98 | if (entry == -1) 99 | { 100 | if (result->name) 101 | { 102 | int c = *ending; 103 | *ending = 0; 104 | //fprintf(stderr, "tex_enc_load: parse errors, already named (%s and %s)\n", result->name, name); 105 | *ending = c; 106 | } 107 | result->name = name; 108 | } 109 | else if (entry <= 255) 110 | { 111 | result->entries[entry] = name; 112 | entry++; 113 | } 114 | else 115 | { 116 | int c = *ending; 117 | *ending = 0; 118 | //fprintf(stderr, "tex_enc_load: extra entry %s", name); 119 | *ending = c; 120 | } 121 | } 122 | } 123 | *ending = 0; 124 | 125 | if (entry < 256) 126 | fprintf(stderr, "tex_enc_load: incomplete encoding, %d entries\n", entry); 127 | } 128 | fz_catch(ctx) 129 | { 130 | if (result) 131 | fz_free(ctx, result); 132 | if (buffer) 133 | fz_drop_buffer(ctx, buffer); 134 | fz_rethrow(ctx); 135 | } 136 | 137 | return result; 138 | } 139 | 140 | void tex_enc_free(fz_context *ctx, tex_enc *t) 141 | { 142 | fz_drop_buffer(ctx, t->buffer); 143 | fz_free(ctx, t); 144 | } 145 | 146 | const char *tex_enc_get(tex_enc *t, uint8_t code) 147 | { 148 | return t->entries[code]; 149 | } 150 | -------------------------------------------------------------------------------- /src/proxy.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #ifdef __APPLE__ 34 | # include 35 | #else 36 | # include 37 | #endif 38 | 39 | static int usage(void) 40 | { 41 | fprintf(stderr, "Usage:\n proxy [directory with texpresso.{stdin,stdout,stderr}]\n"); 42 | return 1; 43 | } 44 | 45 | static int transfer(int fd_src, int fd_dst) 46 | { 47 | char buffer[1024]; 48 | int n; 49 | 50 | do 51 | { 52 | n = read(fd_src, buffer, 1024); 53 | if (n == -1) 54 | { 55 | if (errno == EINTR) 56 | continue; 57 | perror("transfer read"); 58 | return 0; 59 | } 60 | } while (0); 61 | 62 | char *ptr = buffer; 63 | while (n > 0) 64 | { 65 | int m = write(fd_dst, ptr, n); 66 | if (m == -1) 67 | { 68 | if (errno == EINTR) 69 | continue; 70 | perror("transfer write"); 71 | return 0; 72 | } 73 | n -= m; 74 | ptr += m; 75 | } 76 | return 1; 77 | } 78 | 79 | int main(int argc, char **argv) 80 | { 81 | char 82 | path_stdin[PATH_MAX], 83 | path_stdout[PATH_MAX], 84 | path_stderr[PATH_MAX]; 85 | 86 | const char *tmpdir = getenv("TMPDIR"); 87 | stpcpy(stpcpy(path_stdin, tmpdir), "/texpresso.stdin"); 88 | stpcpy(stpcpy(path_stdout, tmpdir), "/texpresso.stdout"); 89 | stpcpy(stpcpy(path_stderr, tmpdir), "/texpresso.stderr"); 90 | 91 | int fd_stdin = open(path_stdin, O_WRONLY, 0); 92 | if (fd_stdin == -1) 93 | { 94 | perror(path_stdin); 95 | return 1; 96 | } 97 | int fd_stdout = open(path_stdout, O_RDONLY, 0); 98 | if (fd_stdout == -1) 99 | { 100 | perror(path_stdout); 101 | return 1; 102 | } 103 | int fd_stderr = open(path_stderr, O_RDONLY, 0); 104 | if (fd_stderr == -1) 105 | { 106 | perror(path_stderr); 107 | return 1; 108 | } 109 | 110 | while (1) 111 | { 112 | struct pollfd fds[3]; 113 | fds[0].fd = 0; 114 | fds[0].events = POLLIN; 115 | fds[0].revents = 0; 116 | fds[1].fd = fd_stdout; 117 | fds[1].events = POLLIN; 118 | fds[1].revents = 0; 119 | fds[2].fd = fd_stderr; 120 | fds[2].events = POLLIN; 121 | fds[2].revents = 0; 122 | 123 | int n = poll(fds, 3, -1); 124 | if (n == -1) 125 | { 126 | if (errno == EINTR) continue; 127 | perror("proxy poll"); 128 | break; 129 | } 130 | 131 | if (fds[0].revents & POLLNVAL || 132 | fds[1].revents & POLLNVAL || 133 | fds[2].revents & POLLNVAL) 134 | { 135 | fprintf(stderr, "proxy: stream closed\n"); 136 | break; 137 | } 138 | 139 | if (fds[0].revents & POLLIN) 140 | if (!transfer(fds[0].fd, fd_stdin)) 141 | return 1; 142 | 143 | if (fds[1].revents & POLLIN) 144 | if (!transfer(fds[1].fd, STDOUT_FILENO)) 145 | return 1; 146 | 147 | if (fds[2].revents & POLLIN) 148 | if (!transfer(fds[2].fd, STDERR_FILENO)) 149 | return 1; 150 | } 151 | 152 | if (close(fd_stdin) == -1) 153 | perror("close stdin"); 154 | if (close(fd_stdout) == -1) 155 | perror("close stdout"); 156 | if (close(fd_stderr) == -1) 157 | perror("close stderr"); 158 | 159 | return 0; 160 | } 161 | -------------------------------------------------------------------------------- /src/dvi/dvi_context.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #include "mydvi.h" 26 | 27 | dvi_context *dvi_context_new(fz_context *ctx, dvi_reshooks hooks) 28 | { 29 | dvi_context *dc = fz_malloc_struct(ctx, dvi_context); 30 | 31 | dc->dev = NULL; 32 | dc->resmanager = dvi_resmanager_new(ctx, hooks); 33 | dvi_scratch_init(&dc->scratch); 34 | 35 | dvi_state *st = &dc->root; 36 | st->fonts = dvi_fonttable_new(ctx); 37 | st->gs.ctm = fz_identity; 38 | st->registers_stack.base = &dc->registers_stack[0]; 39 | st->registers_stack.depth = 0; 40 | st->registers_stack.limit = sizeof(dc->registers_stack)/sizeof(dc->registers_stack[0]); 41 | st->gs_stack.base = &dc->gs_stack[0]; 42 | st->gs_stack.depth = 0; 43 | st->gs_stack.limit = sizeof(dc->gs_stack)/sizeof(dc->gs_stack[0]); 44 | return dc; 45 | } 46 | 47 | static void 48 | dvi_context_set_device(fz_context *ctx, dvi_context *dc, fz_device *dev) 49 | { 50 | if (dev == dc->dev) 51 | return; 52 | if (dc->dev) 53 | fz_drop_device(ctx, dc->dev); 54 | dc->dev = dev; 55 | if (dev) 56 | fz_keep_device(ctx, dev); 57 | } 58 | 59 | void dvi_context_free(fz_context *ctx, dvi_context *dc) 60 | { 61 | dvi_context_set_device(ctx, dc, NULL); 62 | dvi_resmanager_free(ctx, dc->resmanager); 63 | dvi_scratch_release(ctx, &dc->scratch); 64 | fz_free(ctx, dc); 65 | } 66 | 67 | void dvi_context_begin_frame(fz_context *ctx, dvi_context *dc, fz_device *dev) 68 | { 69 | dvi_context_set_device(ctx, dc, dev); 70 | dvi_state *st = &dc->root; 71 | st->registers_stack.depth = 0; 72 | st->gs = (dvi_graphicstate){0,}; 73 | st->gs.ctm = fz_identity; 74 | st->gs.ctm.d = -1; 75 | st->gs.ctm.e = 72; 76 | st->gs.ctm.f = 72; 77 | st->gs_stack.depth = 0; 78 | 79 | dc->colorstack.depth = 0; 80 | for (int i = 0; i < dc->pdfcolorstacks.capacity; i++) 81 | dc->pdfcolorstacks.stacks[i].depth = 0; 82 | } 83 | 84 | void dvi_context_end_frame(fz_context *ctx, dvi_context *dc) 85 | { 86 | dvi_scratch_clear(ctx, &dc->scratch); 87 | dvi_context_set_device(ctx, dc, NULL); 88 | 89 | if (dc->colorstack.depth > 0) 90 | fprintf(stderr, "default color stack: ending frame with %d colors\n", dc->colorstack.depth); 91 | 92 | for (int i = 0; i < dc->pdfcolorstacks.capacity; i++) 93 | if (dc->pdfcolorstacks.stacks[i].depth > 0) 94 | fprintf(stderr, "default color stack: ending frame with %d colors\n", 95 | dc->pdfcolorstacks.stacks[i].depth); 96 | } 97 | 98 | dvi_state *dvi_context_state(dvi_context *dc) 99 | { 100 | return &dc->root; 101 | } 102 | 103 | bool dvi_state_enter_vf(dvi_context *dc, dvi_state *vfst, const dvi_state *st, dvi_fonttable *fonts, int font, fixed_t scale) 104 | { 105 | float s = fixed_double(scale); 106 | vfst->version = DVI_VF; 107 | vfst->f = font; 108 | vfst->gs = st->gs; 109 | vfst->gs.ctm = fz_pre_scale(dvi_get_ctm(dc, st), s, s); 110 | vfst->gs.h = vfst->gs.v = 0; 111 | vfst->registers.h = 0; 112 | vfst->registers.v = 0; 113 | vfst->registers.x = 0; 114 | vfst->registers.y = 0; 115 | vfst->registers.w = 0; 116 | vfst->registers.z = 0; 117 | vfst->registers_stack.base = st->registers_stack.base + st->registers_stack.depth; 118 | vfst->registers_stack.limit = st->registers_stack.limit - st->registers_stack.depth; 119 | vfst->registers_stack.depth = 0; 120 | vfst->gs_stack.base = st->gs_stack.base + st->gs_stack.depth; 121 | vfst->gs_stack.limit = st->gs_stack.limit - st->gs_stack.depth; 122 | vfst->gs_stack.depth = 0; 123 | vfst->fonts = fonts; 124 | return 1; 125 | } 126 | 127 | -------------------------------------------------------------------------------- /src/dvi/intcodec.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #ifndef __INTCODEC_H__ 26 | #define __INTCODEC_H__ 27 | 28 | #include 29 | #include 30 | #include "fixed.h" 31 | 32 | static uint8_t decode_u8(const uint8_t *buf) 33 | { return buf[0]; } 34 | 35 | static uint16_t decode_u16(const uint8_t *buf) 36 | { return (buf[0] << 8) | (buf[1]); } 37 | 38 | static uint32_t decode_u24(const uint8_t *buf) 39 | { return (buf[0] << 16) | (buf[1] << 8) | (buf[2]); } 40 | 41 | static uint32_t decode_u32(const uint8_t *buf) 42 | { return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | (buf[3]); } 43 | 44 | static int8_t decode_s8(const uint8_t *buf) 45 | { return ((int8_t*)buf)[0]; } 46 | 47 | static int16_t decode_s16(const uint8_t *buf) 48 | { return (decode_s8(buf) << 8) | (buf[1]); } 49 | 50 | static int32_t decode_s24(const uint8_t *buf) 51 | { return (decode_s8(buf) << 16) | (buf[1] << 8) | (buf[2]); } 52 | 53 | static int32_t decode_s32(const uint8_t *buf) 54 | { return (decode_s8(buf) << 24) | (buf[1] << 16) | (buf[2] << 8) | (buf[3]); } 55 | 56 | static fixed_t decode_fixed(const uint8_t *buf) 57 | { return fixed_make(decode_s32(buf)); } 58 | 59 | static int32_t decode_sB(const uint8_t *buf, int n) 60 | { 61 | switch (n) 62 | { 63 | case 1: return decode_s8(buf); 64 | case 2: return decode_s16(buf); 65 | case 3: return decode_s24(buf); 66 | case 4: return decode_s32(buf); 67 | default: abort(); 68 | } 69 | } 70 | 71 | static uint32_t decode_uB(const uint8_t *buf, int n) 72 | { 73 | switch (n) 74 | { 75 | case 1: return decode_u8(buf); 76 | case 2: return decode_u16(buf); 77 | case 3: return decode_u24(buf); 78 | case 4: return decode_u32(buf); 79 | default: abort(); 80 | } 81 | } 82 | 83 | static uint8_t read_u8(const uint8_t **buf) 84 | { 85 | uint8_t result = decode_u8(*buf); 86 | *buf += 1; 87 | return result; 88 | } 89 | 90 | static uint16_t read_u16(const uint8_t **buf) 91 | { 92 | uint16_t result = decode_u16(*buf); 93 | *buf += 2; 94 | return result; 95 | } 96 | 97 | static uint32_t read_u24(const uint8_t **buf) 98 | { 99 | uint32_t result = decode_u24(*buf); 100 | *buf += 3; 101 | return result; 102 | } 103 | 104 | static uint32_t read_u32(const uint8_t **buf) 105 | { 106 | uint32_t result = decode_u32(*buf); 107 | *buf += 4; 108 | return result; 109 | } 110 | 111 | static int8_t read_s8(const uint8_t **buf) 112 | { 113 | int8_t result = decode_s8(*buf); 114 | *buf += 1; 115 | return result; 116 | } 117 | 118 | static int16_t read_s16(const uint8_t **buf) 119 | { 120 | int16_t result = decode_s16(*buf); 121 | *buf += 2; 122 | return result; 123 | } 124 | 125 | static int32_t read_s24(const uint8_t **buf) 126 | { 127 | int32_t result = decode_s24(*buf); 128 | *buf += 3; 129 | return result; 130 | } 131 | 132 | static int32_t read_s32(const uint8_t **buf) 133 | { 134 | int32_t result = decode_s32(*buf); 135 | *buf += 4; 136 | return result; 137 | } 138 | 139 | static fixed_t read_fixed(const uint8_t **buf) 140 | { 141 | fixed_t result = decode_fixed(*buf); 142 | *buf += 4; 143 | return result; 144 | } 145 | 146 | static int32_t read_sB(const uint8_t **buf, int n) 147 | { 148 | int32_t result = decode_sB(*buf, n); 149 | *buf += n; 150 | return result; 151 | } 152 | 153 | static uint32_t read_uB(const uint8_t **buf, int n) 154 | { 155 | uint32_t result = decode_uB(*buf, n); 156 | *buf += n; 157 | return result; 158 | } 159 | 160 | #endif /*__INTCODEC_H__*/ 161 | -------------------------------------------------------------------------------- /test/test_utf_mapping.output: -------------------------------------------------------------------------------- 1 | # Test 1. test_ascii: Basic ASCII (1-byte UTF-8, 1 UTF-16 code unit) 2 | 3 | | H | e | l | l | o | 0A | 4 | ^ ^ ^ ^ ^ ^ 5 | [error] Invalid UTF-16 range: pointing past end of line 6 | 7 | # Test 2. test_2byte: Single 2-byte UTF-8 (é) - valid, should advance 1 UTF-16 unit 8 | 9 | | c | a | f | C3 | A9 | 0A | 10 | ^ ^ ^ ^ ^_ 11 | [error] Invalid UTF-16 range: pointing past end of line 12 | [error] Invalid UTF-16 range: pointing past end of line 13 | 14 | # Test 3. test_3byte: Single 3-byte UTF-8 (€) - valid, should advance 1 UTF-16 unit 15 | 16 | | c | a | f | E2 | 82 | AC | 0A | 17 | ^ ^ ^ ^ ^__ 18 | [error] Invalid UTF-16 range: pointing in the middle of a surrogate pair 19 | [error] Invalid UTF-16 range: pointing past end of line 20 | [error] Invalid UTF-16 range: pointing past end of line 21 | 22 | # Test 4. test_4byte: Single 4-byte UTF-8 (🌈) - valid, should advance 2 UTF-16 units 23 | 24 | | c | a | f | F0 | 9F | 8C | 88 | 0A | 25 | ^ ^ ^ ^_ ^__ 26 | [error] Invalid UTF-16 range: pointing in the middle of a surrogate pair 27 | [error] Invalid UTF-16 range: pointing past end of line 28 | [error] Invalid UTF-16 range: pointing past end of line 29 | [error] Invalid UTF-16 range: pointing past end of line 30 | [error] Invalid UTF-16 range: pointing past end of line 31 | [error] Invalid UTF-16 range: pointing past end of line 32 | 33 | # Test 5. test_mixed: Mixed: 1-byte + 2-byte + 3-byte + 4-byte 34 | 35 | | A | C3 | A9 | E2 | 82 | AC | F0 | 9F | 8C | 88 | 0A | 36 | ^ ^ ^ ^_ ^_____ 37 | [warning] UTF-16 range: unexpected continuation byte 38 | [error] Invalid UTF-16 range: pointing past end of line 39 | [warning] UTF-16 range: unexpected continuation byte 40 | [error] Invalid UTF-16 range: pointing past end of line 41 | [warning] UTF-16 range: unexpected continuation byte 42 | [error] Invalid UTF-16 range: pointing past end of line 43 | 44 | # Test 6. test_invalid_continuation: Invalid continuation byte (no leading byte) 45 | 46 | | 80 | 81 | 82 | 0A | 47 | ^___ 48 | [error] Invalid UTF-16 range: pointing past end of line 49 | [error] Invalid UTF-16 range: pointing past end of line 50 | 51 | # Test 7. test_overlong: Overlong encoding (e.g., 2-byte for U+0000) 52 | 53 | | C0 | 80 | C1 | BF | 0A | 54 | ^ ^ ^__ 55 | [error] Invalid UTF-16 range: pointing past end of line 56 | [error] Invalid UTF-16 range: pointing past end of line 57 | 58 | # Test 8. test_surrogate: Surrogate pair in UTF-8 (invalid, should trigger error, U+D800) 59 | 60 | | ED | A0 | 80 | 0A | 61 | ^ ^__ 62 | [error] Invalid UTF-16 range: pointing past end of line 63 | [error] Invalid UTF-16 range: pointing past end of line 64 | [error] Invalid UTF-16 range: pointing past end of line 65 | [error] Invalid UTF-16 range: pointing past end of line 66 | 67 | # Test 9. test_surrogate_pair: Surrogate pair (U+D800 + U+DFFF) 68 | 69 | | ED | A0 | 80 | ED | AF | BF | 0A | 70 | ^ ^ ^____ 71 | [error] Invalid UTF-16 range: pointing past end of buffer 72 | 73 | # Test 10. test_nl_in_middle_2byte: Line feed in the middle of a codepoint (should fail) 74 | 75 | | c | a | E9 | 0A | 76 | ^ ^ ^_ 77 | [error] Broken UTF-8 encoding: line return in the middle of a codepoint 78 | [error] Broken UTF-8 encoding: line return in the middle of a codepoint 79 | 80 | # Test 11. test_nl_in_middle_3byte: \xE2\x82 is incomplete 3-byte 81 | 82 | | c | a | E2 | 82 | 0A | 83 | ^ ^ ^__ 84 | [error] Broken UTF-8 encoding: line return in the middle of a codepoint 85 | [error] Broken UTF-8 encoding: line return in the middle of a codepoint 86 | [error] Broken UTF-8 encoding: line return in the middle of a codepoint 87 | 88 | # Test 12. test_nl_in_middle_4byte: incomplete 4-byte 89 | 90 | | c | a | F0 | 90 | 8C | 0A | 91 | ^ ^ ^___ 92 | [error] Broken UTF-8 encoding: line return in the middle of a codepoint 93 | [error] Broken UTF-8 encoding: line return in the middle of a codepoint 94 | 95 | # Test 13. test_nl_after_surrogate: Line feed after partial surrogate pair (high surrogate, then \n) 96 | 97 | | ED | A0 | 0A | 98 | ^__ 99 | [warning] UTF-16 range: unexpected continuation byte 100 | [error] Invalid UTF-16 range: pointing past end of line 101 | [warning] UTF-16 range: unexpected continuation byte 102 | [error] Invalid UTF-16 range: pointing past end of line 103 | [warning] UTF-16 range: unexpected continuation byte 104 | [error] Invalid UTF-16 range: pointing past end of line 105 | [warning] UTF-16 range: unexpected continuation byte 106 | [error] Invalid UTF-16 range: pointing past end of line 107 | 108 | # Test 14. test_trailing_continuation: Trailing continuation bytes (no leading byte) 109 | 110 | | 80 | 81 | 82 | 83 | 0A | 111 | ^____ 112 | [error] Invalid UTF-16 range: pointing past end of buffer 113 | [error] Invalid UTF-16 range: pointing past end of buffer 114 | 115 | # Test 15. test_end_mid_4byte: incomplete 4-byte 116 | 117 | | c | a | f | C3 | A9 | F0 | 90 | 8C | 118 | ^ ^ ^ ^ ^___ 119 | 120 | # Test 16. test_middle_surrogate: Pointing in middle of surrogate pair (should fail, U+D800 not followed by low surrogate) 121 | 122 | | ED | A0 | 80 | 123 | ^ ^_ 124 | 125 | -------------------------------------------------------------------------------- /src/sprotocol.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #ifndef SPROTOCOL_H 26 | #define SPROTOCOL_H 27 | 28 | #include 29 | #include 30 | #include 31 | #include "myabort.h" 32 | 33 | typedef struct channel_s channel_t; 34 | typedef int file_id; 35 | 36 | #define LOG 0 37 | 38 | #define LEN(txt) (sizeof(txt)-1) 39 | #define STR(X) #X 40 | #define SSTR(X) STR(X) 41 | 42 | #define pabort() \ 43 | do { perror(__FILE__ ":" SSTR(__LINE__)); myabort(); } while(0) 44 | 45 | #define mabort(...) \ 46 | do { fprintf(stderr, "Aborting from " __FILE__ ":" SSTR(__LINE__) "\n" __VA_ARGS__); print_backtrace(); abort(); } while(0) 47 | 48 | #define PACK(a,b,c,d) ((d << 24) | (c << 16) | (b << 8) | a) 49 | 50 | /* QUERIES */ 51 | 52 | enum query { 53 | Q_OPEN = PACK('O','P','E','N'), 54 | Q_READ = PACK('R','E','A','D'), 55 | Q_WRIT = PACK('W','R','I','T'), 56 | Q_CLOS = PACK('C','L','O','S'), 57 | Q_SIZE = PACK('S','I','Z','E'), 58 | Q_SEEN = PACK('S','E','E','N'), 59 | Q_GPIC = PACK('G','P','I','C'), 60 | Q_SPIC = PACK('S','P','I','C'), 61 | Q_CHLD = PACK('C','H','L','D'), 62 | }; 63 | 64 | struct pic_cache { 65 | int type, page; 66 | float bounds[4]; 67 | }; 68 | 69 | typedef struct { 70 | int time; 71 | enum query tag; 72 | union { 73 | struct { 74 | file_id fid; 75 | char *path, *mode; 76 | } open; 77 | struct { 78 | file_id fid; 79 | int pos, size; 80 | } read; 81 | struct { 82 | file_id fid; 83 | int pos, size; 84 | char *buf; 85 | } writ; 86 | struct { 87 | file_id fid; 88 | } clos; 89 | struct { 90 | file_id fid; 91 | } size; 92 | struct { 93 | file_id fid; 94 | int pos; 95 | } seen; 96 | struct { 97 | int fd; 98 | int pid; 99 | } chld; 100 | struct { 101 | char *path; 102 | int type, page; 103 | } gpic; 104 | struct { 105 | char *path; 106 | struct pic_cache cache; 107 | } spic; 108 | }; 109 | } query_t; 110 | 111 | /* ANSWERS */ 112 | 113 | enum answer { 114 | A_DONE = PACK('D','O','N','E'), 115 | A_PASS = PACK('P','A','S','S'), 116 | A_SIZE = PACK('S','I','Z','E'), 117 | A_READ = PACK('R','E','A','D'), 118 | A_FORK = PACK('F','O','R','K'), 119 | A_OPEN = PACK('O','P','E','N'), 120 | A_GPIC = PACK('G','P','I','C'), 121 | }; 122 | 123 | enum accs_answer { 124 | ACCS_PASS = 0, 125 | ACCS_OK = 1, 126 | ACCS_ENOENT = 2, 127 | ACCS_EACCES = 3, 128 | }; 129 | 130 | struct stat_time { 131 | uint32_t sec, nsec; 132 | }; 133 | 134 | struct stat_answer { 135 | uint32_t dev, ino; 136 | uint32_t mode; 137 | uint32_t nlink; 138 | uint32_t uid, gid; 139 | uint32_t rdev; 140 | uint32_t size; 141 | uint32_t blksize, blocks; 142 | struct stat_time atime, ctime, mtime; 143 | }; 144 | 145 | #define READ_FORK (-1) 146 | 147 | typedef struct { 148 | enum answer tag; 149 | union { 150 | struct { 151 | int size; 152 | } size; 153 | struct { 154 | int size; 155 | } read; 156 | struct { 157 | int size; 158 | } open; 159 | struct { 160 | float bounds[4]; 161 | } gpic; 162 | }; 163 | } answer_t; 164 | 165 | /* "ASK" :P */ 166 | 167 | enum ask { 168 | C_FLSH = PACK('F','L','S','H'), 169 | }; 170 | 171 | typedef struct { 172 | enum ask tag; 173 | union { 174 | struct { 175 | int pid; 176 | } term; 177 | struct { 178 | int fid, pos; 179 | } fenc; 180 | struct { 181 | int fid; 182 | } flsh; 183 | }; 184 | } ask_t; 185 | 186 | /* Functions */ 187 | 188 | channel_t *channel_new(void); 189 | void channel_free(channel_t *c); 190 | 191 | bool channel_handshake(channel_t *c, int fd); 192 | bool channel_has_pending_query(channel_t *t, int fd, int timeout); 193 | enum query channel_peek_query(channel_t *t, int fd); 194 | bool channel_read_query(channel_t *t, int fd, query_t *r); 195 | void channel_write_ask(channel_t *t, int fd, ask_t *a); 196 | void channel_write_answer(channel_t *t, int fd, answer_t *a); 197 | void *channel_get_buffer(channel_t *t, size_t n); 198 | void channel_flush(channel_t *t, int fd); 199 | void channel_reset(channel_t *t); 200 | 201 | void log_query(FILE *f, query_t *q); 202 | #endif /*!SPROTOCOL_H*/ 203 | -------------------------------------------------------------------------------- /src/dvi/vstack.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #ifndef VSTACK_H_ 26 | #define VSTACK_H_ 27 | 28 | #include 29 | #include 30 | 31 | typedef struct vstack vstack; 32 | 33 | vstack *vstack_new(fz_context *ctx); 34 | void vstack_free(fz_context *ctx, vstack *t); 35 | void vstack_reset(fz_context *ctx, vstack *t); 36 | 37 | void vstack_push_null(fz_context *ctx, vstack *t); 38 | void vstack_push_number(fz_context *ctx, vstack *t, float value); 39 | void vstack_push_bool(fz_context *ctx, vstack *t, bool value); 40 | void vstack_push_ref(fz_context *ctx, vstack *t, int obj, int gen); 41 | 42 | void vstack_begin_array(fz_context *ctx, vstack *t); 43 | void vstack_end_array(fz_context *ctx, vstack *t); 44 | 45 | void vstack_begin_dict(fz_context *ctx, vstack *t); 46 | void vstack_end_dict(fz_context *ctx, vstack *t); 47 | 48 | void vstack_begin_string(fz_context *ctx, vstack *t); 49 | void vstack_end_string(fz_context *ctx, vstack *t); 50 | 51 | void vstack_begin_hexstring(fz_context *ctx, vstack *t); 52 | void vstack_end_hexstring(fz_context *ctx, vstack *t); 53 | 54 | void vstack_begin_name(fz_context *ctx, vstack *t); 55 | void vstack_end_name(fz_context *ctx, vstack *t); 56 | 57 | void vstack_push_char(fz_context *ctx, vstack *t, int c); 58 | void vstack_push_chars(fz_context *ctx, vstack *t, const void *data, size_t len); 59 | 60 | enum val_kind 61 | { 62 | VAL_NUMBER = 2, 63 | VAL_BOOL, 64 | VAL_NULL, 65 | VAL_STRING, 66 | VAL_HEXSTRING, 67 | VAL_NAME, 68 | VAL_ARRAY, 69 | VAL_DICT, 70 | VAL_REF, 71 | }; 72 | 73 | typedef struct 74 | { 75 | enum val_kind kind : 4; 76 | uint32_t length : 28; 77 | union 78 | { 79 | float f; 80 | bool b; 81 | uint32_t o; 82 | }; 83 | } val; 84 | 85 | val vstack_get_values(fz_context *ctx, vstack *t); 86 | void vstack_get_arguments(fz_context *ctx, vstack *t, val *values, int count); 87 | void vstack_get_floats(fz_context *ctx, vstack *t, float *values, int count); 88 | 89 | #define inlined static inline __attribute__((unused)) 90 | 91 | inlined bool val_is_null(val v) { return (v.kind == VAL_NULL); } 92 | inlined bool val_is_number(val v) { return (v.kind == VAL_NUMBER); } 93 | inlined bool val_is_bool(val v) { return (v.kind == VAL_BOOL); } 94 | inlined bool val_is_string(val v) { return (v.kind == VAL_STRING || v.kind == VAL_HEXSTRING); } 95 | inlined bool val_is_name(val v) { return (v.kind == VAL_NAME); } 96 | inlined bool val_is_array(val v) { return (v.kind == VAL_ARRAY); } 97 | inlined bool val_is_dict(val v) { return (v.kind == VAL_DICT); } 98 | inlined bool val_is_ref(val v) { return (v.kind == VAL_REF); } 99 | 100 | #define VAL_CHECK(v, kind) \ 101 | if (!val_is_##kind(v)) \ 102 | fz_throw(ctx, 0, "%s: value is not a %s", __func__, #kind) 103 | 104 | inlined float val_number(fz_context *ctx, val v) 105 | { VAL_CHECK(v, number); return v.f; } 106 | 107 | inlined bool val_bool(fz_context *ctx, val v) 108 | { VAL_CHECK(v, bool); return v.b; } 109 | 110 | inlined uint32_t val_string_length(fz_context *ctx, vstack *t, val v) 111 | { VAL_CHECK(v, string); return v.length; } 112 | 113 | inlined uint32_t val_array_length(fz_context *ctx, vstack *t, val v) 114 | { VAL_CHECK(v, array); return v.length; } 115 | 116 | inlined uint32_t val_dict_length(fz_context *ctx, vstack *t, val v) 117 | { VAL_CHECK(v, dict); return v.length; } 118 | 119 | inlined uint32_t val_ref_obj(fz_context *ctx, vstack *t, val v) 120 | { VAL_CHECK(v, dict); return v.o; } 121 | 122 | inlined uint32_t val_ref_gen(fz_context *ctx, vstack *t, val v) 123 | { VAL_CHECK(v, dict); return v.length; } 124 | 125 | #undef inlined 126 | #undef VAL_CHECK 127 | 128 | bool vstack_at_top_level(vstack *t); 129 | val val_array_get(fz_context *ctx, vstack *t, val array, int index); 130 | val val_dict_get_key(fz_context *ctx, vstack *t, val array, int index); 131 | val val_dict_get_value(fz_context *ctx, vstack *t, val array, int index); 132 | const char *val_string(fz_context *ctx, vstack *t, val v); 133 | const char *val_as_string(fz_context *ctx, vstack *t, val v); 134 | const char *val_as_name(fz_context *ctx, vstack *t, val v); 135 | 136 | bool vstack_in_string(vstack *t); 137 | bool vstack_in_name(vstack *t); 138 | bool vstack_in_dict(vstack *t); 139 | bool vstack_in_dict_value(vstack *t); 140 | bool vstack_in_array(vstack *t); 141 | 142 | #endif // VSTACK_H_ 143 | -------------------------------------------------------------------------------- /src/fs.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include "state.h" 27 | #include "mupdf_compat.h" 28 | #include "dvi/fz_util.h" 29 | 30 | static unsigned long 31 | sdbm_hash(const unsigned char *str) 32 | { 33 | unsigned long hash = 0; 34 | int c; 35 | 36 | while ((c = *str++)) 37 | hash = c + (hash << 6) + (hash << 16) - hash; 38 | 39 | return hash * 2654435761; 40 | } 41 | 42 | #define str_hash sdbm_hash 43 | 44 | typedef struct 45 | { 46 | unsigned long hash; 47 | fileentry_t *entry; 48 | } tablecell; 49 | 50 | struct filesystem_s 51 | { 52 | int count, cap; 53 | tablecell *table; 54 | }; 55 | 56 | static const char *normalize_path(const char *path) 57 | { 58 | if (path[0] == '.' && path[1] == '/') 59 | { 60 | path += 2; 61 | while (*path == '/') 62 | path += 1; 63 | } 64 | return path; 65 | } 66 | 67 | filesystem_t *filesystem_new(fz_context *ctx) 68 | { 69 | fz_ptr(filesystem_t, fs); 70 | fz_ptr(tablecell, table); 71 | 72 | fz_try(ctx) 73 | { 74 | fs = fz_malloc_struct(ctx, filesystem_t); 75 | fs->cap = 64; 76 | table = fz_malloc_struct_array(ctx, 64, tablecell); 77 | fs->table = table; 78 | } 79 | fz_catch(ctx) 80 | { 81 | if (table) 82 | fz_free(ctx, table); 83 | if (fs) 84 | fz_free(ctx, fs); 85 | fz_rethrow(ctx); 86 | } 87 | return fs; 88 | } 89 | 90 | void filesystem_free(fz_context *ctx, filesystem_t *fs) 91 | { 92 | int cap = fs->cap; 93 | for (int i = 0; i < cap; ++i) 94 | { 95 | fileentry_t *e = fs->table[i].entry; 96 | if (!e) 97 | continue; 98 | if (e->fs_data) 99 | fz_drop_buffer(ctx, e->fs_data); 100 | if (e->edit_data) 101 | fz_drop_buffer(ctx, e->edit_data); 102 | if (e->saved.data) 103 | fz_drop_buffer(ctx, e->saved.data); 104 | fz_free(ctx, (void *)e->path); 105 | fz_free(ctx, fs->table[i].entry); 106 | } 107 | fz_free(ctx, fs->table); 108 | fz_free(ctx, fs); 109 | } 110 | 111 | static tablecell *table_get(int cap, tablecell *table, const char *path) 112 | { 113 | unsigned long mask = cap - 1; 114 | unsigned long hash = str_hash((const unsigned char*)path); 115 | 116 | int index = hash & mask; 117 | 118 | while (table[index].entry) 119 | { 120 | if (table[index].hash == hash && strcmp(table[index].entry->path, path) == 0) 121 | break; 122 | index = (index + 1) & mask; 123 | } 124 | table[index].hash = hash; 125 | return &table[index]; 126 | } 127 | 128 | static tablecell *table_resize(fz_context *ctx, int oldcap, tablecell *oldtab, int newcap) 129 | { 130 | tablecell *newtab = fz_malloc_struct_array(ctx, newcap, tablecell); 131 | int mask = newcap - 1; 132 | for (int i = 0; i < oldcap; ++i) 133 | { 134 | if (!oldtab[i].entry) 135 | continue; 136 | tablecell cell = oldtab[i]; 137 | int index = cell.hash & mask; 138 | while (newtab[index].entry) 139 | { 140 | if ((cell.hash & mask) < (newtab[index].hash & mask)) 141 | { 142 | tablecell tmp = newtab[index]; 143 | newtab[index] = cell; 144 | cell = tmp; 145 | } 146 | index = (index + 1) & mask; 147 | } 148 | newtab[index] = cell; 149 | } 150 | return newtab; 151 | } 152 | 153 | fileentry_t *filesystem_lookup(filesystem_t *fs, const char *path) 154 | { 155 | return table_get(fs->cap, fs->table, normalize_path(path))->entry; 156 | } 157 | 158 | fileentry_t *filesystem_lookup_or_create(fz_context *ctx, filesystem_t *fs, const char *path) 159 | { 160 | path = normalize_path(path); 161 | tablecell *cell = table_get(fs->cap, fs->table, path); 162 | fileentry_t *entry = cell->entry; 163 | 164 | if (entry != NULL) return entry; 165 | 166 | entry = fz_malloc_struct(ctx, fileentry_t); 167 | entry->path = fz_strdup(ctx, path); 168 | entry->saved.level = FILE_NONE; 169 | entry->seen = -1; 170 | entry->pic_cache.type = -1; 171 | entry->fs_stat.st_ino = 0; 172 | cell->entry = entry; 173 | 174 | fs->count += 1; 175 | if (fs->count * 4 >= fs->cap * 3) 176 | { 177 | int newcap = fs->cap * 2; 178 | tablecell *newtab = table_resize(ctx, fs->cap, fs->table, newcap); 179 | fz_free(ctx, fs->table); 180 | fs->cap = newcap; 181 | fs->table = newtab; 182 | } 183 | 184 | return entry; 185 | } 186 | 187 | fileentry_t *filesystem_scan(filesystem_t *fs, int *index) 188 | { 189 | while (*index < fs->cap) 190 | { 191 | int i = *index; 192 | *index += 1; 193 | if (fs->table[i].entry) 194 | return fs->table[i].entry; 195 | } 196 | return NULL; 197 | } 198 | -------------------------------------------------------------------------------- /src/engine.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #ifndef GENERIC_ENGINE_H_ 26 | #define GENERIC_ENGINE_H_ 27 | 28 | #include "state.h" 29 | #include "incdvi.h" 30 | #include "synctex.h" 31 | 32 | #define send(method, ...) \ 33 | (send__extract_first(__VA_ARGS__, NULL)->_class->method((txp_engine*)__VA_ARGS__)) 34 | 35 | #define send__extract_first(x, ...) (x) 36 | 37 | typedef struct txp_engine_s txp_engine; 38 | 39 | txp_engine *txp_create_tex_engine(fz_context *ctx, 40 | const char *tectonic_path, 41 | const char *inclusion_path, 42 | const char *tex_dir, 43 | const char *tex_name); 44 | 45 | txp_engine *txp_create_pdf_engine(fz_context *ctx, const char *pdf_path); 46 | 47 | txp_engine *txp_create_dvi_engine(fz_context *ctx, 48 | const char *tectonic_path, 49 | const char *dvi_dir, 50 | const char *dvi_path); 51 | 52 | typedef enum { 53 | DOC_RUNNING, 54 | DOC_TERMINATED 55 | } txp_engine_status; 56 | 57 | struct txp_engine_s { 58 | struct txp_engine_class *_class; 59 | }; 60 | 61 | struct txp_engine_class 62 | { 63 | void (*destroy)(txp_engine *self, fz_context *ctx); 64 | bool (*step)(txp_engine *self, fz_context *ctx, bool restart_if_needed); 65 | void (*begin_changes)(txp_engine *self, fz_context *ctx); 66 | void (*detect_changes)(txp_engine *self, fz_context *ctx); 67 | bool (*end_changes)(txp_engine *self, fz_context *ctx); 68 | int (*page_count)(txp_engine *self); 69 | fz_display_list *(*render_page)(txp_engine *self, fz_context *ctx, int page); 70 | txp_engine_status (*get_status)(txp_engine *self); 71 | float (*scale_factor)(txp_engine *self); 72 | synctex_t *(*synctex)(txp_engine *self, fz_buffer **buf); 73 | fileentry_t *(*find_file)(txp_engine *self, fz_context *ctx, const char *path); 74 | void (*notify_file_changes)(txp_engine *self, fz_context *ctx, fileentry_t *entry, int offset); 75 | }; 76 | 77 | #define TXP_ENGINE_DEF_CLASS \ 78 | static void engine_destroy(txp_engine *_self, fz_context *ctx); \ 79 | static fz_display_list *engine_render_page(txp_engine *_self, \ 80 | fz_context *ctx, int page); \ 81 | static bool engine_step(txp_engine *_self, fz_context *ctx, \ 82 | bool restart_if_needed); \ 83 | static void engine_begin_changes(txp_engine *_self, fz_context *ctx); \ 84 | static void engine_detect_changes(txp_engine *_self, fz_context *ctx); \ 85 | static bool engine_end_changes(txp_engine *_self, fz_context *ctx); \ 86 | static int engine_page_count(txp_engine *_self); \ 87 | static txp_engine_status engine_get_status(txp_engine *_self); \ 88 | static float engine_scale_factor(txp_engine *_self); \ 89 | static synctex_t *engine_synctex(txp_engine *_self, fz_buffer **buf); \ 90 | static fileentry_t *engine_find_file(txp_engine *_self, fz_context *ctx, \ 91 | const char *path); \ 92 | static void engine_notify_file_changes(txp_engine *self, fz_context *ctx, \ 93 | fileentry_t *entry, int offset); \ 94 | \ 95 | static struct txp_engine_class _class = { \ 96 | .destroy = engine_destroy, \ 97 | .step = engine_step, \ 98 | .page_count = engine_page_count, \ 99 | .render_page = engine_render_page, \ 100 | .get_status = engine_get_status, \ 101 | .scale_factor = engine_scale_factor, \ 102 | .synctex = engine_synctex, \ 103 | .find_file = engine_find_file, \ 104 | .begin_changes = engine_begin_changes, \ 105 | .detect_changes = engine_detect_changes, \ 106 | .end_changes = engine_end_changes, \ 107 | .notify_file_changes = engine_notify_file_changes, \ 108 | } 109 | 110 | #endif // GENERIC_ENGINE_H_ 111 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Building and installing TeXpresso 2 | 3 | If all dependencies are installed and out-of-the-box configuration works, `make all` should be sufficient to build both `texpresso` and `texpresso-tonic` in `build/` directory. 4 | 5 | ## Supported systems 6 | 7 | TeXpresso is in an early stage of development and its configuration logic is a rough hand-made script. 8 | So far it has only been tested the following systems, where we expect it to work: 9 | 10 | - OSX 11 | - Fedora 39 12 | - Arch Linux: [a PKGBUILD is available in the AUR](https://aur.archlinux.org/packages/texpresso-git) that builds from the latest Git HEAD on installation. 13 | - Debian 12 14 | - Ubuntu 22.04 15 | 16 | On other systems you may observe build failures that require modifying the Makefile. Let us know if it works on a system not listed above, or if you can tweak the configuration/build code to support your system without breaking others. 17 | 18 | **Rerun `make config` when you change the build environment**, otherwise freshly installed libraries might not be considered by the build system. 19 | 20 | ### Ubuntu 21 | 22 | (Tested with Ubuntu 22.04 ARM64 and Ubuntu 20.04) 23 | 24 | Install all needed dependencies with: 25 | ```sh 26 | apt install build-essential libsdl2-dev libmupdf-dev libmujs-dev libfreetype-dev libgumbo-dev libjbig2dec0-dev libjpeg-dev libopenjp2-7-dev cargo libssl-dev libfontconfig-dev libleptonica-dev libharfbuzz-dev 27 | ``` 28 | 29 | Details: 30 | - `build-essential` install the compiler (GCC) and basic build tools (GNU Make) 31 | - `libsdl2-dev`: SDL2 library 32 | - `libmupdf-dev libmujs-dev libfreetype-dev libgumbo-dev libjbig2dec0-dev libjpeg-dev libopenjp2-7-dev`: libmupdf and its dependencies 33 | - `cargo libssl-dev libfontconfig-dev`: rust package manager, and dependencies needed by texpresso-tonic rust code 34 | 35 | ### Debian 12 36 | 37 | Debian 12 is quite similar to Ubuntu with the added difficulty that the rust version is too old for TeXpresso to build out of the box. 38 | 39 | You can install the other dependencies: 40 | 41 | ```sh 42 | sudo apt install build-essential libsdl2-dev libmupdf-dev libfreetype-dev libjpeg-dev libjbig2dec0-dev libharfbuzz-dev libopenjp2-7-dev libgumbo-dev libmujs-dev libssl-dev libfontconfig-dev 43 | ``` 44 | 45 | A workaround for rust is to install [rustup](https://rustup.rs). Make sure that curl is installed and setup rustup: 46 | 47 | ```sh 48 | sudo apt install curl 49 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 50 | ``` 51 | 52 | After rustup is installed, source the environment before building. E.g: 53 | ```sh 54 | source $HOME/.cargo/env 55 | ``` 56 | 57 | ### Arch Linux (and Manjaro) 58 | 59 | Dependencies are listed in the PKGBUILD, but if you need to install them manually: 60 | 61 | ```sh 62 | pacman -S base-devel fontconfig freetype2 gcc-libs glibc graphite gumbo-parser harfbuzz icu jbig2dec libjpeg-turbo libmupdf libpng openjpeg2 openssl sdl2 zlib cargo git libmupdf 63 | ``` 64 | 65 | ### Fedora 66 | 67 | (Tested on Fedora 38 ARM64) 68 | 69 | Install all dependencies: 70 | 71 | ```sh 72 | sudo dnf install make gcc mupdf-devel SDL2-devel g++ freetype2-devel libjpeg-turbo-devel jbig2dec-devel openjpeg2-devel gumbo-parser-devel tesseract-devel leptonica-devel cargo openssl-devel fontconfig-devel 73 | ``` 74 | 75 | ### OSX 76 | 77 | (Tested on Ventura Intel and Sonoma Apple Sillicon) 78 | 79 | Install the following dependencies with homebrew: 80 | 81 | ```sh 82 | brew install rust mupdf-tools SDL2 83 | ``` 84 | 85 | Note: `mupdf-tools` can be replaced by `mupdf`, either is fine. 86 | 87 | ## Download 88 | 89 | Simply clone the git repository (and its submodules) using one of the following commands: 90 | 91 | ``` 92 | git clone --recurse-submodules https://github.com/let-def/texpresso.git # cloning by HTTP 93 | git clone --recurse-submodules git@github.com:let-def/texpresso.git # cloning by SSH 94 | ``` 95 | 96 | (You may want to adjust the URL if you are looking at a different fork.) 97 | 98 | Note that while TeXpresso itself (the driver/viewer program) is small (less than 2MiB of sources, about 40MiB once built), the `tectonic` LaTeX engine that we include as a submodule is large -- 120MiB of sources, most of it from its `harfbuzz` dependency, and about 1.2GiB once built. 99 | 100 | ## Build TeXpresso 101 | 102 | First make sure the dependencies are available: `pkg-config`, `SDL2`, `mupdf` (and its own dependencies: `libjpeg`, `libpng`, `freetype2`, `gumbo`, `jbig2dec`... and possibly `leptonica`, `tesseract` and `mujs` depending on the mupdf version). 103 | Under macOS, `brew` is also used to find local files. 104 | 105 | If it succeeds, `make texpresso` produces `build/texpresso`. 106 | 107 | Other targets are: 108 | - `config` to generate configuration in `Makefile.config` (automatically called during first build) 109 | - `dev` produces `build/texpresso-dev` which supports hot-reloading to ease development 110 | - `debug` produces debugging tools in `build/` 111 | - `clean` to remove intermediate build files 112 | - `distclean` to remove all build files (`build/` and `Makefile.config`) 113 | 114 | If the build fails, try tweaking the configuration flags in `Makefile.config`. 115 | 116 | ## Build TeXpresso-tonic (Tectonic) 117 | 118 | First you need an environment that is able to build Tectonic: a functional rust 119 | and cargo installation, etc. Check tectonic documentation. 120 | 121 | Then make sure that the git submodule has been initialized (in the `tectonic` directory): 122 | 123 | ```sh 124 | git submodule update --init --recursive 125 | ``` 126 | 127 | Then `make texpresso-tonic` should work. 128 | 129 | ## Testing TeXpresso 130 | 131 | If both commands built successfully, you can try TeXpresso using: 132 | 133 | ```sh 134 | build/texpresso test/simple.tex 135 | ``` 136 | 137 | This is just a minimal test to make sure that TeXpresso is installed correctly. 138 | If TeXpresso window does not display the document, please report an issue. 139 | 140 | ## Using TeXpresso 141 | 142 | [README.md](./README.md) has information on supported editors and how to control the TeXpresso viewer. 143 | -------------------------------------------------------------------------------- /src/dvi/tex_vf.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include "mydvi.h" 27 | #include "intcodec.h" 28 | #include "fz_util.h" 29 | 30 | struct tex_vf 31 | { 32 | fz_buffer *buffer; 33 | uint8_t comment_len, *comment; 34 | uint32_t checksum; 35 | fixed_t design_size; 36 | dvi_fonttable *fonts; 37 | tex_vf_char *chars; 38 | int char_cap; 39 | int default_font; 40 | }; 41 | 42 | enum vf_op 43 | { 44 | LONG_CHAR = 242, 45 | FNT_DEF1 = 243, 46 | FNT_DEF2 = 244, 47 | FNT_DEF3 = 245, 48 | FNT_DEF4 = 246, 49 | PRE = 247, 50 | POST = 248 51 | }; 52 | 53 | static tex_vf_char *tex_vf_at(fz_context *ctx, tex_vf *vf, int code) 54 | { 55 | if (code >= vf->char_cap) 56 | { 57 | int capacity = 8; 58 | while (capacity <= code) 59 | capacity *= 2; 60 | tex_vf_char *chars = fz_malloc_struct_array(ctx, capacity, tex_vf_char); 61 | if (vf->chars) 62 | { 63 | memcpy(chars, vf->chars, sizeof(tex_vf_char) * vf->char_cap); 64 | fz_free(ctx, vf->chars); 65 | } 66 | vf->chars = chars; 67 | vf->char_cap = capacity; 68 | } 69 | return &vf->chars[code]; 70 | } 71 | 72 | #define FAIL(...) fz_throw(ctx, 0, "tex_vf_load: " __VA_ARGS__) 73 | 74 | tex_vf *tex_vf_load(fz_context *ctx, dvi_resmanager *manager, fz_stream *stm) 75 | { 76 | fz_ptr(fz_buffer, buffer); 77 | fz_ptr(tex_vf, vf); 78 | 79 | fz_try(ctx) 80 | { 81 | buffer = fz_read_all(ctx, stm, 4096); 82 | fz_trim_buffer(ctx, buffer); 83 | vf = fz_malloc_struct(ctx, tex_vf); 84 | 85 | if (buffer->len < 16) FAIL("file is too small"); 86 | if (buffer->data[0] != PRE) FAIL("file doesn't start with preamble"); 87 | if (buffer->data[1] != DVI_VF) FAIL("invalid preamble ID"); 88 | 89 | vf->buffer = buffer; 90 | vf->comment_len = buffer->data[2]; 91 | vf->comment = buffer->data + 3; 92 | vf->fonts = dvi_fonttable_new(ctx); 93 | vf->default_font = -1; 94 | 95 | const uint8_t 96 | *cursor = buffer->data + 3 + vf->comment_len, 97 | *end = buffer->data + buffer->len; 98 | 99 | vf->checksum = read_u32(&cursor); 100 | vf->design_size = fixed_make(read_s32(&cursor)); 101 | 102 | while (cursor < end) 103 | { 104 | uint8_t op = read_u8(&cursor); 105 | if (op == POST) 106 | break; 107 | else if (op <= LONG_CHAR) 108 | { 109 | uint32_t len, code, width; 110 | 111 | if (op == LONG_CHAR) 112 | { 113 | if (cursor + 12 >= end) FAIL("truncated file"); 114 | len = read_u32(&cursor); 115 | code = read_u32(&cursor); 116 | width = read_u32(&cursor); 117 | } 118 | else 119 | { 120 | if (cursor + 4 >= end) FAIL("truncated file"); 121 | len = op; 122 | code = read_u8(&cursor); 123 | width = read_u24(&cursor); 124 | } 125 | if (cursor + len >= end) 126 | FAIL("truncated file (or DVI program is too long)"); 127 | 128 | tex_vf_char *c = tex_vf_at(ctx, vf, code); 129 | c->dvi = cursor; 130 | c->dvi_length = len; 131 | c->width = fixed_make(width); 132 | cursor += len; 133 | } 134 | else if (op >= FNT_DEF1 && op <= FNT_DEF4) 135 | { 136 | int n = op - FNT_DEF1 + 1; 137 | if (cursor + n + 13 >= end) 138 | FAIL("truncated file"); 139 | uint32_t font_id = read_uB(&cursor, n); 140 | if (vf->default_font == -1) 141 | vf->default_font = font_id; 142 | uint32_t checksum = read_u32(&cursor); 143 | fixed_t scale_factor = fixed_make(read_s32(&cursor)); 144 | fixed_t design_size = fixed_make(read_s32(&cursor)); 145 | int name_len = 0; 146 | name_len += read_u8(&cursor); 147 | name_len += read_u8(&cursor); 148 | const char *name = (const char *)cursor; 149 | cursor += name_len; 150 | 151 | // int c = *cursor; 152 | // *(char*)cursor = 0; 153 | // fprintf(stderr, "vf: defining font %s\n", name); 154 | // *(char*)cursor = c; 155 | 156 | if (cursor > end) 157 | FAIL("truncated file"); 158 | 159 | dvi_fontdef *fontdef = dvi_fonttable_get(ctx, vf->fonts, font_id); 160 | if (!fontdef) abort(); 161 | 162 | fontdef->kind = TEX_FONT; 163 | fontdef->tex_font.font = dvi_resmanager_get_tex_font(ctx, manager, name, name_len); 164 | fontdef->tex_font.spec.checksum = checksum; 165 | fontdef->tex_font.spec.scale_factor = scale_factor; 166 | fontdef->tex_font.spec.design_size = design_size; 167 | } 168 | else 169 | FAIL("invalid opcode"); 170 | } 171 | 172 | if (cursor > end) 173 | FAIL("truncated file"); 174 | } 175 | fz_catch(ctx) 176 | { 177 | if (buffer) 178 | fz_drop_buffer(ctx, buffer); 179 | if (vf) 180 | { 181 | if (vf->fonts) 182 | dvi_fonttable_free(ctx, vf->fonts); 183 | fz_free(ctx, vf); 184 | } 185 | fz_rethrow(ctx); 186 | } 187 | 188 | return vf; 189 | } 190 | 191 | tex_vf_char *tex_vf_get(tex_vf *vf, int code) 192 | { 193 | if (code >= 0 && code < vf->char_cap) 194 | { 195 | tex_vf_char *c = &vf->chars[code]; 196 | if (c->dvi) 197 | return c; 198 | } 199 | 200 | return NULL; 201 | } 202 | 203 | dvi_fonttable *tex_vf_fonttable(tex_vf *vf) 204 | { 205 | return vf->fonts; 206 | } 207 | 208 | int tex_vf_default_font(tex_vf *vf) 209 | { 210 | return vf->default_font; 211 | } 212 | -------------------------------------------------------------------------------- /src/incdvi.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include "incdvi.h" 28 | #include "mydvi.h" 29 | #include "mydvi_interp.h" 30 | #include "mydvi_opcodes.h" 31 | 32 | struct incdvi_s 33 | { 34 | int offset; 35 | int fontdef_offset; 36 | int page_len, page_cap; 37 | int *pages; 38 | dvi_context *dc; 39 | }; 40 | 41 | static int add_page(fz_context *ctx, incdvi_t *d) 42 | { 43 | int result = d->page_len; 44 | if (result == d->page_cap) 45 | { 46 | if (d->page_cap == 0) 47 | { 48 | d->pages = fz_malloc_struct_array(ctx, 8, int); 49 | d->page_cap = 8; 50 | } 51 | else 52 | { 53 | int *pages = fz_malloc_struct_array(ctx, d->page_cap * 2, int); 54 | memcpy(pages, d->pages, sizeof(int) * d->page_cap); 55 | fz_free(ctx, d->pages); 56 | d->pages = pages; 57 | d->page_cap = d->page_cap * 2; 58 | } 59 | } 60 | d->page_len += 1; 61 | return result; 62 | } 63 | 64 | incdvi_t *incdvi_new(fz_context *ctx, dvi_reshooks hooks) 65 | { 66 | incdvi_t *d = fz_malloc_struct(ctx, incdvi_t); 67 | d->dc = dvi_context_new(ctx, hooks); 68 | return d; 69 | } 70 | 71 | void incdvi_free(fz_context *ctx, incdvi_t *d) 72 | { 73 | if (d->pages) 74 | fz_free(ctx, d->pages); 75 | dvi_context_free(ctx, d->dc); 76 | fz_free(ctx, d); 77 | } 78 | 79 | void incdvi_reset(incdvi_t *d) 80 | { 81 | d->offset = 0; 82 | d->fontdef_offset = 0; 83 | d->page_len = 0; 84 | } 85 | 86 | void incdvi_update(fz_context *ctx, incdvi_t *d, fz_buffer *buf) 87 | { 88 | if (buf == NULL) 89 | { 90 | incdvi_reset(d); 91 | return; 92 | } 93 | 94 | int len = buf->len; 95 | 96 | if (d->offset > len) 97 | { 98 | while (d->page_len > 0 && d->pages[d->page_len - 1] >= len) 99 | d->page_len -= 1; 100 | if (d->page_len == 0) 101 | d->offset = 0; 102 | else 103 | { 104 | d->page_len -= 1; 105 | d->offset = d->pages[d->page_len]; 106 | } 107 | } 108 | 109 | if (d->offset == 0) 110 | { 111 | if (d->page_len != 0) abort(); 112 | int plen = dvi_preamble_size(buf->data, len); 113 | if (plen > 0) 114 | { 115 | if (dvi_preamble_parse(ctx, d->dc, dvi_context_state(d->dc), buf->data)) 116 | d->offset = plen; 117 | } 118 | } 119 | 120 | if (d->offset > 0) 121 | { 122 | enum dvi_version version = dvi_context_state(d->dc)->version; 123 | while (d->offset < len) 124 | { 125 | int ilen = dvi_instr_size(buf->data + d->offset, len - d->offset, version); 126 | if (ilen <= 0) 127 | break; 128 | if (buf->data[d->offset] == BOP || buf->data[d->offset] == EOP) 129 | { 130 | int page = add_page(ctx, d); 131 | if (!(page & 1) != (buf->data[d->offset] == BOP)) 132 | abort(); 133 | d->pages[page] = d->offset; 134 | } 135 | d->offset += ilen; 136 | } 137 | } 138 | 139 | if (d->fontdef_offset > d->offset) 140 | d->fontdef_offset = d->offset; 141 | } 142 | 143 | bool incdvi_output_started(incdvi_t *d) 144 | { 145 | return (d->page_len > 0); 146 | } 147 | 148 | int incdvi_page_count(incdvi_t *d) 149 | { 150 | return (d->page_len / 2); 151 | } 152 | 153 | static void incdvi_parse_fontdef(fz_context *ctx, incdvi_t *restrict d, fz_buffer *buf, int offset) 154 | { 155 | if (offset > buf->len) abort(); 156 | enum dvi_version version = dvi_context_state(d->dc)->version; 157 | while (d->fontdef_offset < offset) 158 | { 159 | int ilen = dvi_instr_size( 160 | buf->data + d->fontdef_offset, 161 | offset - d->fontdef_offset, 162 | version 163 | ); 164 | if (ilen <= 0) 165 | break; 166 | if (buf->data[d->fontdef_offset] >= XXX1 && buf->data[d->fontdef_offset] <= XXX4) 167 | dvi_interp_init(ctx, d->dc, buf->data + d->fontdef_offset, offset - d->fontdef_offset); 168 | if (dvi_is_fontdef(buf->data[d->fontdef_offset])) 169 | { 170 | dvi_interp(ctx, d->dc, buf->data + d->fontdef_offset); 171 | } 172 | d->fontdef_offset += ilen; 173 | } 174 | } 175 | 176 | void incdvi_page_dim(incdvi_t *d, fz_buffer *buf, int page, float *width, float *height, bool *landscape) 177 | { 178 | bool _landscape; 179 | if (!landscape) landscape = &_landscape; 180 | *landscape = 0; 181 | if (page < 0 || page >= incdvi_page_count(d)) abort(); 182 | int bop = d->pages[page * 2]; 183 | if (dvi_interp_bop(buf->data + bop, buf->len - bop, width, height, landscape) <= 0) 184 | abort(); 185 | if (*landscape) 186 | { 187 | float tmp = *width; 188 | *width = *height; 189 | *height = tmp; 190 | } 191 | } 192 | 193 | void incdvi_render_page(fz_context *ctx, incdvi_t *d, fz_buffer *buf, int page, fz_device *dev) 194 | { 195 | if (page < 0 || page >= incdvi_page_count(d)) abort(); 196 | int offset = d->pages[page * 2]; 197 | int eop = d->pages[page * 2 + 1]; 198 | incdvi_parse_fontdef(ctx, d, buf, offset); 199 | 200 | dvi_context *dc = d->dc; 201 | enum dvi_version version = dvi_context_state(dc)->version; 202 | dvi_context_begin_frame(ctx, d->dc, dev); 203 | while (offset < eop) 204 | { 205 | int ilen = dvi_instr_size(buf->data + offset, eop - offset, version); 206 | if (ilen <= 0) abort(); 207 | dvi_interp(ctx, dc, buf->data + offset); 208 | offset += ilen; 209 | } 210 | dvi_context_end_frame(ctx, dc); 211 | } 212 | 213 | float incdvi_tex_scale_factor(incdvi_t *d) 214 | { 215 | if (d->page_len == 0) 216 | return 1; 217 | return d->dc->scale; 218 | } 219 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | texpresso 5 | 6 |

7 | 8 | TeXpresso: live rendering and error reporting for LaTeX 9 | 10 |

11 |
12 | 13 | 14 | > [!Note] 15 | > TeXpresso is still in an early development phase. 16 | > Changes and bug fixes are happening frequently, check the [CHANGELOG.md](CHANGELOG.md). 17 | 18 | **Important: this repository uses submodules. Clone using `git clone --recurse-submodules`.** 19 | 20 | ## About 21 | 22 | TeXpresso provides a "live rendering" experience when editing LaTeX documents in a supported editor: change something in the .tex file, the render window will update almost immediately with your change. Write something invalid, you get an error message immediately. 23 | 24 | This can radically improve the LaTeX editing experience compared to the usual rebuild-and-wait-for-viewer-to-update experience, especially for large documents. 25 | 26 | See the [screencasts](#Screencasts) at the end of this file for a visual demo of TeXpresso capabilities. 27 | 28 | ### Install 29 | 30 | TeXpresso has been tested on Linux and macOS and should work with both AMD64 and Apple Silicon architectures. See [INSTALL.md](./INSTALL.md) for dependency and build instructions. 31 | 32 | ### Design 33 | 34 | The TeXpresso system is built of the following parts: 35 | 36 | 1. A TeX engine that renders LaTeX documents into PDF; 37 | we use a modified version of the [Tectonic](https://tectonic-typesetting.github.io/en-US/) engine, modified to interact with the TeXpresso driver. 38 | 39 | This is in the [tectonic/](tectonic/) git-submodule, and it produces the `texpresso-tonic` helper binary 40 | 41 | 2. A PDF renderer that renders PDF documents into images. 42 | We use [MuPDF](https://mupdf.com/). 43 | 44 | 3. A viewer that shows the rendered images and allows simple user commands (see [Viewer controls](#Viewer_controls) below), built with [libSDL](https://www.libsdl.org/). 45 | 46 | 4. A driver program that talks to the editor to be notified of changes to the LaTeX document, maintains an incremental view of the document and the rendering process (supporting incrementality, rollback, error recovery, etc.), talks to the LaTeX engine to re-render the modified portions of the document, and synchronizes with the viewer. 47 | 48 | The driver is where the "live" magic lives. It is the `texpresso` binary, whose sources are in this repository. 49 | 50 | The driver sends information between the editor and the renderer in both directions. In particular, it is possible to ask the editor to jump to a specific place in the LaTeX document by clicking on the viewer window or, conversely, to refresh the viewer window to display the document at the editor position. 51 | 52 | ## Viewer controls 53 | 54 | Keyboard controls: 55 | - `←`, `→`: change page 56 | - `↑`, `↓`: move within the page 57 | - `p` (for "page"): switch between "fit-to-page" and "fit-to-width" zoom modes 58 | - `c` ("crop"): crop borders 59 | - `q` ("quit"): quit 60 | - `i` ("invert"): dark mode 61 | - `I` : toggle theming 62 | - `t` ("top"): toggle stay-on-top (keeping TeXpresso above the editor window) 63 | - `b` ("border"): toggle window borders 64 | - `F5`: start fullscreen presentation (leave with `ESC`) 65 | 66 | Mouse controls: 67 | 68 | - click: select text in window (TODO: move Emacs buffer with SyncTeX) 69 | - control+click: pan page 70 | - wheel: scroll page 71 | - control+wheel: zoom 72 | 73 | ## Supported editors 74 | 75 | ### Emacs 76 | 77 | TeXpresso comes with an Emacs mode. The source can be found in 78 | [emacs/texpresso.el](emacs/texpresso.el). Load this file in Emacs (using `M-X load-file`; it is also compatible with `require`). 79 | 80 | Start TeXpresso with `M-x texpresso`. The prompt will let you select the master/root TeX file. 81 | It will try to start the `texpresso` command. If it is not possible, it will open 82 | `(customize-variable 'texpresso-binary)` to let you set the path to texpresso 83 | binary (`/build/texpresso`). 84 | 85 | To work correctly, `texpresso` needs `texpresso-tonic` helper; when copying them, make sure they are both in the same directory. 86 | 87 | `M-x texpresso-display-output` will open a small window listing TeX warnings and errors on the current page. 88 | Use `M-x texpresso-next-page` and `M-x texpresso-previous-page` to move between pages without leaving Emacs. 89 | 90 | ### Neovim 91 | 92 | A Neovim mode is provided in a separate repository [texpresso.vim](https://github.com/let-def/texpresso.vim). It is not yet compatible with vanilla Vim, patches are welcome :bow:. 93 | 94 | ### Visual Studio Code 95 | 96 | A vscode mode is being developed in [texpresso-vscode](https://github.com/DominikPeters/texpresso-vscode), thanks to @DominikPeters. 97 | Look for [TeXpresso](https://marketplace.visualstudio.com/items?itemName=DominikPeters.texpresso-basic) in the marketplace. 98 | 99 | ## Screencasts 100 | 101 | **Neovim integration.** 102 | Launching TeXpresso in vim: 103 | 104 | https://github.com/let-def/texpresso.vim/assets/1048096/b6a1966a-52ca-4e2e-bf33-e83b6af851d8 105 | 106 | Live update during edition: 107 | 108 | https://github.com/let-def/texpresso.vim/assets/1048096/cfdff380-992f-4732-a1fa-f05584930610 109 | 110 | Using Quickfix window to fix errors and warnings interactively: 111 | 112 | https://github.com/let-def/texpresso.vim/assets/1048096/e07221a9-85b1-44f3-a904-b4f7d6bcdb9b 113 | 114 | Synchronization from Document to Editor (SyncTeX backward): 115 | 116 | https://github.com/let-def/texpresso.vim/assets/1048096/f69b1508-a069-4003-9578-662d9e790ff9 117 | 118 | Synchronization from Editor to Document (SyncTeX forward): 119 | 120 | https://github.com/let-def/texpresso.vim/assets/1048096/78560d20-391e-490e-ad76-c8cce1004ce5 121 | 122 | Theming, Light/Dark modes: 😎 123 | 124 | https://github.com/let-def/texpresso.vim/assets/1048096/a072181b-82d3-42df-9683-7285ed1b32fc 125 | 126 | **Emacs integration.** Here is a sample recording of me editing and browsing @fabiensanglard [Game Engine Black Book: Doom](https://github.com/fabiensanglard/gebbdoom) in TeXpresso (using my emacs theme): 127 | 128 | https://user-images.githubusercontent.com/1048096/235424858-a5a2900b-fb48-40b7-a167-d0b71af39034.mp4 129 | 130 | ## Credits 131 | 132 | Thanks to [@DominikPeters](https://github.com/DominikPeters) for contributing the VS Code extension. 133 | Thanks to [@merv1n34k](https://github.com/merv1n34k) for contributing the logo. 134 | 135 | Thanks our many contributors, [@gasche](https://github.com/gasche), [@haselwarter](https://github.com/haselwarter), [@alerque](https://github.com/alerque), [@sandersantema](https://github.com/sandersantema), [@t4ccer](https://github.com/t4ccer), [@schnell18](https://github.com/schnell18), and [@bowentan](https://github.com/bowentan) for improving features, portability and compatibility with many platforms. 136 | -------------------------------------------------------------------------------- /src/state.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include "state.h" 27 | #include "string.h" 28 | #include "mupdf_compat.h" 29 | #include "dvi/fz_util.h" 30 | 31 | /* Rollback log */ 32 | 33 | #undef LOG 34 | #define LOG 0 35 | 36 | enum log_action { 37 | LOG_ENTRY = 0x42, 38 | LOG_CELL, 39 | LOG_OVERWRITE 40 | }; 41 | 42 | struct log_s 43 | { 44 | mark_t snap; 45 | fz_buffer *data; 46 | }; 47 | 48 | log_t *log_new(fz_context *ctx) 49 | { 50 | fz_ptr(log_t, log); 51 | fz_ptr(fz_buffer, data); 52 | fz_try(ctx) 53 | { 54 | log = fz_malloc_struct(ctx, log_t); 55 | data = fz_new_buffer(ctx, 512); 56 | log->data = data; 57 | log->snap = 1; 58 | fz_append_byte(ctx, log->data, 0); 59 | } 60 | fz_catch(ctx) 61 | { 62 | if (data) 63 | fz_drop_buffer(ctx, data); 64 | if (log) 65 | fz_free(ctx, log); 66 | fz_rethrow(ctx); 67 | } 68 | return log; 69 | } 70 | 71 | void log_free(fz_context *ctx, log_t *log) 72 | { 73 | fz_drop_buffer(ctx, log->data); 74 | fz_free(ctx, log); 75 | } 76 | 77 | #define PUSH_VALUE(ctx, buf, val) \ 78 | fz_append_data(ctx, buf, &(val), sizeof(val)) 79 | 80 | static void pop_value(fz_buffer *buf, void *val, size_t len) 81 | { 82 | if (buf->len < len) abort(); 83 | buf->len -= len; 84 | memcpy(val, buf->data + buf->len, len); 85 | } 86 | 87 | #define POP_VALUE(buf, val) \ 88 | pop_value(buf, &(val), sizeof(val)) 89 | 90 | static void push_action(fz_context *ctx, fz_buffer *buf, enum log_action action) 91 | { 92 | uint8_t b = action; 93 | PUSH_VALUE(ctx, buf, b); 94 | } 95 | 96 | void log_fileentry(fz_context *ctx, log_t *log, fileentry_t *entry) 97 | { 98 | if (entry->saved.snap != log->snap) 99 | { 100 | if (LOG) fprintf(stderr, "push LOG_ENTRY %s\n", entry->path); 101 | if (entry->saved.data) 102 | { 103 | fz_keep_buffer(ctx, entry->saved.data); 104 | PUSH_VALUE(ctx, log->data, entry->saved.data->len); 105 | } 106 | PUSH_VALUE(ctx, log->data, entry->saved); 107 | PUSH_VALUE(ctx, log->data, entry); 108 | push_action(ctx, log->data, LOG_ENTRY); 109 | entry->saved.snap = log->snap; 110 | } 111 | } 112 | 113 | void log_filecell(fz_context *ctx, log_t *log, filecell_t *cell) 114 | { 115 | if (cell->snap != log->snap) 116 | { 117 | if (LOG) fprintf(stderr, "push LOG_CELL\n"); 118 | PUSH_VALUE(ctx, log->data, *cell); 119 | PUSH_VALUE(ctx, log->data, cell); 120 | push_action(ctx, log->data, LOG_CELL); 121 | cell->snap = log->snap; 122 | } 123 | } 124 | 125 | struct overwrite_data { 126 | fz_buffer *buf; 127 | int start, len; 128 | }; 129 | 130 | void log_overwrite(fz_context *ctx, log_t *log, fz_buffer *buf, int start, int len) 131 | { 132 | if (LOG) fprintf(stderr, "push LOG_OVERWRITE\n"); 133 | fz_keep_buffer(ctx, buf); 134 | fz_append_data(ctx, log->data, buf->data + start, len); 135 | struct overwrite_data data = { 136 | .buf = buf, 137 | .start = start, 138 | .len = len, 139 | }; 140 | PUSH_VALUE(ctx, log->data, data); 141 | push_action(ctx, log->data, LOG_OVERWRITE); 142 | } 143 | 144 | static enum log_action pop_action(fz_buffer *buf) 145 | { 146 | uint8_t b; 147 | POP_VALUE(buf, b); 148 | return b; 149 | } 150 | 151 | static void log_pop(fz_context *ctx, log_t *log) 152 | { 153 | switch (pop_action(log->data)) 154 | { 155 | case LOG_ENTRY: 156 | { 157 | fileentry_t *entry; 158 | POP_VALUE(log->data, entry); 159 | if (LOG) fprintf(stderr, "pop LOG_ENTRY %s\n", entry->path); 160 | if (entry->saved.data) 161 | fz_drop_buffer(ctx, entry->saved.data); 162 | POP_VALUE(log->data, entry->saved); 163 | if (entry->saved.data) 164 | { 165 | POP_VALUE(log->data, entry->saved.data->len); 166 | } 167 | break; 168 | } 169 | case LOG_CELL: 170 | { 171 | if (LOG) fprintf(stderr, "pop LOG_CELL\n"); 172 | filecell_t *cell; 173 | POP_VALUE(log->data, cell); 174 | POP_VALUE(log->data, *cell); 175 | break; 176 | } 177 | case LOG_OVERWRITE: 178 | { 179 | if (LOG) fprintf(stderr, "pop LOG_OVERWRITE\n"); 180 | struct overwrite_data data; 181 | POP_VALUE(log->data, data); 182 | pop_value(log->data, data.buf->data + data.start, data.len); 183 | fz_drop_buffer(ctx, data.buf); 184 | break; 185 | } 186 | default: 187 | abort(); 188 | } 189 | } 190 | 191 | mark_t log_snapshot(fz_context *ctx, log_t *log) 192 | { 193 | return (log->snap = log->data->len); 194 | } 195 | 196 | void log_rollback(fz_context *ctx, log_t *log, mark_t mark) 197 | { 198 | if (mark > log->snap) abort(); 199 | 200 | while (log->data->len > mark) 201 | log_pop(ctx, log); 202 | 203 | if (mark != log->data->len) 204 | { 205 | fprintf(stderr, "[fatal] rollback: mark=%d len =%d\n", mark, (int)log->data->len); 206 | abort(); 207 | } 208 | 209 | log->snap = mark; 210 | } 211 | 212 | /* State */ 213 | 214 | void state_init(state_t *st) 215 | { 216 | memset(st, 0, sizeof(state_t)); 217 | } 218 | 219 | static bool 220 | same_time(struct timespec a, struct timespec b) 221 | { 222 | return (a.tv_sec == b.tv_sec) && (a.tv_nsec == b.tv_nsec); 223 | } 224 | 225 | #ifndef __APPLE__ 226 | # define st_time(a) st_##a##tim 227 | #else 228 | # define st_time(a) st_##a##timespec 229 | #endif 230 | 231 | bool stat_same(struct stat *st1, struct stat *st2) 232 | { 233 | return st1->st_dev == st2->st_dev && 234 | st1->st_ino == st2->st_ino && 235 | st1->st_mode == st2->st_mode && 236 | st1->st_nlink == st2->st_nlink && 237 | st1->st_uid == st2->st_uid && 238 | st1->st_gid == st2->st_gid && 239 | st1->st_rdev == st2->st_rdev && 240 | st1->st_size == st2->st_size && 241 | st1->st_blksize == st2->st_blksize && 242 | st1->st_blocks == st2->st_blocks && 243 | same_time(st1->st_time(a), st2->st_time(a)) && 244 | same_time(st1->st_time(m), st2->st_time(m)) && 245 | same_time(st1->st_time(c), st2->st_time(c)); 246 | } 247 | -------------------------------------------------------------------------------- /src/dvi/mydvi_opcodes.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #ifndef MYDVI_OPCODES_H 26 | #define MYDVI_OPCODES_H 27 | 28 | enum dvi_opcode 29 | { 30 | /* DVI op codes */ 31 | SET_CHAR_0 = 0, 32 | SET_CHAR_1 = 1, 33 | /* etc. */ 34 | SET_CHAR_127 = 127, 35 | SET1 = 128, /* Typesets its single operand between 128 and 255 */ 36 | SET2 = 129, /* Typesets its single two byte unsigned operand */ 37 | SET3 = 130, /* Typesets its single three byte unsigned operand */ 38 | SET4 = 131, /* Typesets its single four byte unsigned operand */ 39 | SET_RULE = 132, /* Sets a rule of height param1(four bytes) and width param2(four bytes) 40 | These are *signed*. Nothing typeset for nonpositive values. 41 | However, negative value *do* change current point */ 42 | PUT1 = 133, /* Like SET1, but point doesn't change */ 43 | PUT2 = 134, /* Like SET2 */ 44 | PUT3 = 135, /* Like SET3 */ 45 | PUT4 = 136, /* Like SET4 */ 46 | PUT_RULE = 137, /* Like SET_RULE */ 47 | NOP = 138, 48 | BOP = 139, /* Followed by 10 four byte count registers (signed?). 49 | Last parameter points to previous BOP (backward linked, first BOP has -1). 50 | BOP clears stack and resets current point. */ 51 | EOP = 140, 52 | PUSH = 141, /* Pushes h,v,w,x,y,z */ 53 | POP = 142, /* Opposite of push*/ 54 | RIGHT1 = 143, /* Move right by one byte signed operand */ 55 | RIGHT2 = 144, /* Move right by two byte signed operand */ 56 | RIGHT3 = 145, /* Move right by three byte signed operand */ 57 | RIGHT4 = 146, /* Move right by four byte signed operand */ 58 | W0 = 147, /* Move right w */ 59 | W1 = 148, /* w <- single byte signed operand. Move right by same amount */ 60 | W2 = 149, /* Same as W1 with two byte signed operand */ 61 | W3 = 150, /* Three byte signed operand */ 62 | W4 = 151, /* Four byte signed operand */ 63 | X0 = 152, /* Move right x */ 64 | X1 = 153, /* Like W1 */ 65 | X2 = 154, /* Like W2 */ 66 | X3 = 155, /* Like W3 */ 67 | X4 = 156, /* Like W4 */ 68 | DOWN1 = 157, /* Move down by one byte signed operand */ 69 | DOWN2 = 158, /* Two byte signed operand */ 70 | DOWN3 = 159, /* Three byte signed operand */ 71 | DOWN4 = 160, /* Four byte signed operand */ 72 | Y0 = 161, /* Move down by y */ 73 | Y1 = 162, /* Move down by one byte signed operand, which replaces Y */ 74 | Y2 = 163, /* Two byte signed operand */ 75 | Y3 = 164, /* Three byte signed operand */ 76 | Y4 = 165, /* Four byte signed operand */ 77 | Z0 = 166, /* Like Y0, but use z */ 78 | Z1 = 167, /* Like Y1 */ 79 | Z2 = 168, /* Like Y2 */ 80 | Z3 = 169, /* Like Y3 */ 81 | Z4 = 170, /* Like Y4 */ 82 | FNT_NUM_0 = 171, /* Switch to font 0 */ 83 | FNT_NUM_1 = 172, /* Switch to font 1 */ 84 | /* etc. */ 85 | FNT_NUM_63 = 234, /* Switch to font 63 */ 86 | FNT1 = 235, /* Switch to font described by single byte unsigned operand */ 87 | FNT2 = 236, /* Switch to font described by two byte unsigned operand */ 88 | FNT3 = 237, /* Three byte font descriptor */ 89 | FNT4 = 238, /* Four byte operator (Knuth says signed, but what would be the point? */ 90 | XXX1 = 239, /* Special. Operand is one byte length. Special follows immediately */ 91 | XXX2 = 240, /* Two byte operand */ 92 | XXX3 = 241, /* Three byte operand */ 93 | XXX4 = 242, /* Four byte operand (Knuth says TeX uses only XXX1 and XXX4 */ 94 | FNT_DEF1 = 243, /* One byte font number, four byte checksum, four byte magnified size (DVI units), four byte designed size, single byte directory length, single byte name length, followed by complete name (area+name) */ 95 | FNT_DEF2 = 244, /* Same for two byte font number */ 96 | FNT_DEF3 = 245, /* Same for three byte font number */ 97 | FNT_DEF4 = 246, /* Four byte font number (Knuth says signed) */ 98 | PRE = 247, /* Preamble: 99 | one byte DVI version (should be 2) 100 | four byte unsigned numerator 101 | four byte unsigned denominator -- one DVI unit = den/num*10^(-7) m 102 | four byte magnification (multiplied by 1000) 103 | one byte unsigned comment length followed by comment. */ 104 | POST = 248, /* Postamble- -- similar to preamble 105 | four byte pointer to final bop 106 | four byte numerator 107 | four byte denominator 108 | four byte mag 109 | four byte maximum height (signed?) 110 | four byte maximum width 111 | two byte max stack depth required to process file 112 | two byte number of pages */ 113 | POST_POST = 249, /* End of postamble 114 | four byte pointer to POST command 115 | Version byte (same as preamble) 116 | Padded by four or more 223's to the end of the file. */ 117 | PADDING = 223, 118 | BEGIN_REFLECT = 250, /* TeX-XeT begin_reflect */ 119 | END_REFLECT = 251, /* TeX-XeT end_reflect */ 120 | 121 | /* XeTeX ".xdv" codes */ 122 | XDV_NATIVE_FONT_DEF = 252 , /* fontdef for native platform font */ 123 | XDV_GLYPHS = 253 , /* string of glyph IDs with X and Y positions */ 124 | XDV_TEXT_GLYPHS = 254 , 125 | XDV_GLYPH_STRING = 1000, /* TODO */ 126 | 127 | PTEXDIR = 255 , /* Ascii pTeX DIR command */ 128 | }; 129 | 130 | enum xdv_flags 131 | { 132 | XDV_FLAG_VERTICAL = 0x0100, 133 | XDV_FLAG_COLORED = 0x0200, 134 | XDV_FLAG_VARIATIONS = 0x0800, 135 | XDV_FLAG_EXTEND = 0x1000, 136 | XDV_FLAG_SLANT = 0x2000, 137 | XDV_FLAG_EMBOLDEN = 0x4000, 138 | XDV_FLAG_ALL = XDV_FLAG_SLANT | XDV_FLAG_EMBOLDEN | XDV_FLAG_VARIATIONS | 139 | XDV_FLAG_EXTEND | XDV_FLAG_COLORED | XDV_FLAG_VERTICAL, 140 | }; 141 | 142 | static inline bool dvi_is_fontdef(uint8_t opcode) 143 | { 144 | return 145 | (opcode >= FNT_DEF1 && opcode <= FNT_DEF4) || 146 | (opcode == XDV_NATIVE_FONT_DEF); 147 | } 148 | 149 | #endif /*!MYDVI_OPCODES_H*/ 150 | -------------------------------------------------------------------------------- /src/sexp_parser.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #include "sexp_parser.h" 26 | 27 | #include 28 | #include 29 | #include "vstack.h" 30 | 31 | static int is_ws(int c) 32 | { 33 | return (c == ' ') || (c == '\n') || (c == '\r') || (c == '\t'); 34 | } 35 | 36 | static int is_initial(int c) 37 | { 38 | return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '!' || 39 | c == '$' || c == '%' || c == '&' || c == '*' || c == '/' || 40 | c == ':' || c == '<' || c == '=' || c == '>' || c == '?' || 41 | c == '_' || c == '^' || c == '-' || c == '+'); 42 | } 43 | 44 | static int is_digit(int c) 45 | { 46 | return (c >= '0') && (c <= '9'); 47 | } 48 | 49 | static int is_octal(int c) 50 | { 51 | return (c >= '0') && (c <= '7'); 52 | } 53 | 54 | static int is_subsequent(int c) 55 | { 56 | return is_initial(c) || is_digit(c); 57 | } 58 | 59 | const sexp_parser initial_sexp_parser = { 60 | .state = P_IDLE, 61 | .number = 0.0, 62 | }; 63 | 64 | const char *sexp_parse(fz_context *ctx, sexp_parser *cp, vstack *stack, const 65 | char *input, const char *limit) 66 | { 67 | while (input < limit) 68 | { 69 | const char *begin = input; 70 | switch (cp->state) 71 | { 72 | case P_IDLE: 73 | while (input < limit) 74 | { 75 | char c = *input; 76 | input += 1; 77 | if (is_ws(c)) 78 | continue; 79 | else if (c == '(') 80 | vstack_begin_array(ctx, stack); 81 | else if (c == ')') 82 | { 83 | vstack_end_array(ctx, stack); 84 | if (vstack_at_top_level(stack)) 85 | return input; 86 | } 87 | else if (c == '"') 88 | { 89 | vstack_begin_string(ctx, stack); 90 | cp->state = P_STRING; 91 | } 92 | else if (is_digit(c)) 93 | { 94 | cp->number = c - '0'; 95 | cp->state = P_POS_NUMBER; 96 | } 97 | else if (c == '+') 98 | { 99 | cp->number = 0; 100 | cp->state = P_POS_NUMBER; 101 | } 102 | else if (c == '-') 103 | { 104 | cp->number = 0; 105 | cp->state = P_NEG_NUMBER; 106 | } 107 | else if (is_initial(c)) 108 | { 109 | vstack_begin_name(ctx, stack); 110 | vstack_push_char(ctx, stack, c); 111 | cp->state = P_IDENT; 112 | } 113 | else 114 | fz_throw(ctx, 0, "sexp parser: unexpected character %C\n", c); 115 | break; 116 | } 117 | break; 118 | 119 | case P_IDENT: 120 | while (input < limit && is_subsequent(*input)) 121 | input += 1; 122 | if (begin != input) 123 | vstack_push_chars(ctx, stack, begin, input - begin); 124 | if (input < limit) 125 | { 126 | vstack_end_name(ctx, stack); 127 | cp->state = P_IDLE; 128 | } 129 | break; 130 | 131 | case P_POS_NUMBER: 132 | case P_NEG_NUMBER: 133 | while (input < limit && is_digit(*input)) 134 | { 135 | cp->number = cp->number * 10.0 + (*input - '0'); 136 | input += 1; 137 | } 138 | if (input >= limit) 139 | break; 140 | if (*input != '.') 141 | { 142 | float n = cp->number; 143 | n = (cp->state == P_POS_NUMBER) ? n : -n; 144 | vstack_push_number(ctx, stack, n); 145 | cp->state = P_IDLE; 146 | break; 147 | } 148 | 149 | input += 1; 150 | cp->state = 151 | (cp->state == P_POS_NUMBER) ? P_POS_NUMBER_FRAC : P_NEG_NUMBER_FRAC; 152 | cp->frac = 0.1; 153 | 154 | case P_POS_NUMBER_FRAC: 155 | case P_NEG_NUMBER_FRAC: 156 | while (input < limit && is_digit(*input)) 157 | { 158 | cp->number = cp->number + cp->frac * (*input - '0'); 159 | cp->frac /= 10.0; 160 | input += 1; 161 | } 162 | if (input < limit) 163 | { 164 | float n = cp->number; 165 | n = (cp->state == P_POS_NUMBER_FRAC) ? n : -n; 166 | vstack_push_number(ctx, stack, n); 167 | cp->state = P_IDLE; 168 | } 169 | break; 170 | 171 | case P_STRING_ESCAPE: 172 | switch (*input) 173 | { 174 | case 'n': 175 | vstack_push_char(ctx, stack, '\n'); 176 | break; 177 | case 'r': 178 | vstack_push_char(ctx, stack, '\r'); 179 | break; 180 | case 't': 181 | vstack_push_char(ctx, stack, '\t'); 182 | break; 183 | case '\t': case ' ': case '\n': case '\r': 184 | break; 185 | default: 186 | if (is_octal(*input)) 187 | { 188 | cp->state = P_STRING_OCTAL1; 189 | cp->octal = (*input - '0'); 190 | input += 1; 191 | continue; 192 | } 193 | else 194 | vstack_push_char(ctx, stack, *input); 195 | break; 196 | } 197 | input += 1; 198 | cp->state = P_STRING; 199 | if (input >= limit) 200 | break; 201 | begin = input; 202 | 203 | case P_STRING: 204 | while (input < limit && *input != '"' && *input != '\\') 205 | input += 1; 206 | if (begin != input) 207 | vstack_push_chars(ctx, stack, begin, input - begin); 208 | if (input < limit) 209 | { 210 | if (*input == '\\') 211 | cp->state = P_STRING_ESCAPE; 212 | else /* *input == '"' */ 213 | { 214 | vstack_end_string(ctx, stack); 215 | cp->state = P_IDLE; 216 | } 217 | input += 1; 218 | } 219 | break; 220 | 221 | case P_STRING_OCTAL1: 222 | if (!is_octal(*input)) 223 | { 224 | vstack_push_char(ctx, stack, cp->octal); 225 | cp->state = P_STRING; 226 | break; 227 | } 228 | cp->octal = cp->octal * 8 + (*input - '0'); 229 | input += 1; 230 | if (input == limit) 231 | { 232 | cp->state = P_STRING_OCTAL2; 233 | break; 234 | } 235 | 236 | case P_STRING_OCTAL2: 237 | if (is_octal(*input)) 238 | { 239 | cp->octal = cp->octal * 8 + (*input - '0'); 240 | input += 1; 241 | } 242 | vstack_push_char(ctx, stack, cp->octal); 243 | cp->state = P_STRING; 244 | break; 245 | } 246 | } 247 | return NULL; 248 | } 249 | -------------------------------------------------------------------------------- /src/driver.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include "logo.h" 29 | #include "driver.h" 30 | 31 | #ifdef __APPLE__ 32 | #include 33 | #else 34 | #include 35 | #endif 36 | 37 | /* Custom SDL events */ 38 | 39 | Uint32 custom_event = 0; 40 | 41 | void schedule_event(enum custom_events ev) 42 | { 43 | static char scheduled[EVENT_COUNT] = {0,}; 44 | 45 | char *sched = scheduled + ev; 46 | if (!*sched) 47 | { 48 | *sched = 1; 49 | SDL_Event event; 50 | SDL_zero(event); 51 | event.type = custom_event; 52 | event.user.code = ev; 53 | event.user.data1 = sched; 54 | if (SDL_PushEvent(&event) < 0) 55 | abort(); 56 | } 57 | } 58 | 59 | static void signal_usr1(int sig) 60 | { 61 | (void)sig; 62 | 63 | static volatile int barrier = 0; 64 | 65 | if (!barrier) 66 | { 67 | barrier = 1; 68 | schedule_event(SCAN_EVENT); 69 | barrier = 0; 70 | } 71 | } 72 | 73 | /* Misc routines */ 74 | 75 | static char *last_index(char *path, char needle) 76 | { 77 | char *result = path; 78 | while (*path) 79 | { 80 | if (*path == needle) 81 | result = path + 1; 82 | path += 1; 83 | } 84 | return result; 85 | } 86 | 87 | static bool get_executable_path(char path[PATH_MAX]) 88 | { 89 | #ifdef __APPLE__ 90 | uint32_t size = PATH_MAX; 91 | char exe_path[PATH_MAX]; 92 | if (_NSGetExecutablePath(exe_path, &size) != 0) 93 | return 0; 94 | #else 95 | char *exe_path = "/proc/self/exe"; 96 | #endif 97 | return realpath(exe_path, path); 98 | } 99 | 100 | static bool should_reload_binary(void) 101 | { 102 | return 0; 103 | } 104 | 105 | int main(int argc, const char **argv) 106 | { 107 | char work_dir[PATH_MAX]; 108 | 109 | if (!getcwd(work_dir, PATH_MAX)) 110 | { 111 | perror("get working directory"); 112 | abort(); 113 | } 114 | 115 | fprintf(stderr, "[info] working directory: %s\n", work_dir); 116 | 117 | char exe_path[PATH_MAX]; 118 | 119 | if (!get_executable_path(exe_path) && !realpath(argv[0], exe_path)) 120 | { 121 | perror("finding executable path"); 122 | abort(); 123 | } 124 | fprintf(stderr, "[info] executable path: %s\n", exe_path); 125 | 126 | const char *doc_arg = NULL; 127 | enum editor_protocol protocol = EDITOR_SEXP; 128 | bool line_output = 0; 129 | 130 | int inclusion_path_size = 1; 131 | for (int i = 1; i < argc; i++) 132 | { 133 | const char *arg = argv[i]; 134 | 135 | if (arg[0] == '-') 136 | { 137 | if (arg[1] == 'j' && 138 | arg[2] == 's' && 139 | arg[3] == 'o' && 140 | arg[4] == 'n' && 141 | arg[5] == '\0') 142 | { 143 | protocol = EDITOR_JSON; 144 | } 145 | else if (arg[1] == 'I' && arg[2] == '\0') 146 | { 147 | i += 1; 148 | if (i == argc) 149 | { 150 | fprintf(stderr, "[error] Expecting a path after -I\n"); 151 | exit(1); 152 | } 153 | inclusion_path_size += 1 + strlen(argv[i]); 154 | } 155 | else if (arg[1] == 'l' && 156 | arg[2] == 'i' && 157 | arg[3] == 'n' && 158 | arg[4] == 'e' && 159 | arg[5] == 's' && 160 | arg[6] == '\0') 161 | { 162 | line_output = 1; 163 | } 164 | else 165 | { 166 | fprintf(stderr, "[error] Unknown option %s\n", arg); 167 | exit(1); 168 | } 169 | 170 | continue; 171 | } 172 | 173 | if (doc_arg == NULL) 174 | { 175 | doc_arg = arg; 176 | continue; 177 | } 178 | 179 | fprintf(stderr, "[error] Expecting a single document argument, got %s and %s\n", 180 | doc_arg, arg); 181 | exit(1); 182 | } 183 | 184 | if (doc_arg == NULL) 185 | { 186 | fprintf(stderr, "Usage: texpresso [-I path]* [-json] root_file.tex\n"); 187 | exit(1); 188 | } 189 | 190 | char *inclusion_path = malloc(inclusion_path_size); 191 | if (!inclusion_path) abort(); 192 | 193 | char *p = inclusion_path; 194 | for (int i = 1; i < argc; i++) 195 | { 196 | if (argv[i][0] == '-' && argv[i][1] == 'I' && argv[i][2] == '\0') 197 | { 198 | i += 1; 199 | p = stpcpy(p, argv[i]) + 1; 200 | } 201 | } 202 | *p = '\0'; 203 | 204 | // Move to TeX document directory 205 | char doc_path[PATH_MAX]; 206 | if (!realpath(doc_arg, doc_path)) 207 | { 208 | perror("finding document path"); 209 | abort(); 210 | } 211 | 212 | char *doc_name = last_index(doc_path, '/'); 213 | if (doc_path == doc_name) abort(); 214 | doc_name[-1] = '\x00'; 215 | 216 | fprintf(stderr, "[info] document path: %s\n", doc_path); 217 | fprintf(stderr, "[info] document name: %s\n", doc_name); 218 | 219 | if (chdir(doc_path) == -1) 220 | { 221 | perror("chdir to document path"); 222 | abort(); 223 | } 224 | 225 | fz_context *ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT); 226 | fz_register_document_handlers(ctx); 227 | 228 | bool init = 0; 229 | 230 | //Initialize SDL 231 | if (init == 0 && SDL_Init(SDL_INIT_VIDEO) < 0) 232 | { 233 | fprintf(stderr, "SDL could not initialize! SDL_Error: %s\n", SDL_GetError()); 234 | abort(); 235 | } 236 | 237 | custom_event = SDL_RegisterEvents(1); 238 | signal(SIGUSR1, signal_usr1); 239 | 240 | //Create window 241 | char window_title[128] = "TeXpresso "; 242 | strcat(window_title, doc_name); 243 | 244 | #if SDL_VERSION_ATLEAST(2, 0, 8) 245 | SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0"); 246 | #endif 247 | 248 | SDL_Window *window; 249 | window = SDL_CreateWindow(window_title, 250 | SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 251 | 700, 900, 252 | SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE 253 | ); 254 | 255 | if (window == NULL) 256 | { 257 | fprintf(stderr, "Window could not be created! SDL_Error: %s\n", SDL_GetError() ); 258 | abort(); 259 | } 260 | 261 | SDL_Surface *logo = texpresso_logo(); 262 | fprintf(stderr, "texpresso logo: %dx%d\n", logo->w, logo->h); 263 | SDL_SetWindowIcon(window, logo); 264 | SDL_FreeSurface(logo); 265 | 266 | SDL_Renderer *renderer; 267 | renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_TARGETTEXTURE); 268 | 269 | struct persistent_state pstate = { 270 | .initial = {0,}, 271 | .protocol = protocol, 272 | .line_output = line_output, 273 | .window = window, 274 | .renderer = renderer, 275 | .ctx = ctx, 276 | .exe_path = exe_path, 277 | .doc_path = doc_path, 278 | .doc_name = doc_name, 279 | .inclusion_path = inclusion_path, 280 | .custom_event = custom_event, 281 | .schedule_event = &schedule_event, 282 | .should_reload_binary = &should_reload_binary, 283 | }; 284 | 285 | while (texpresso_main(&pstate)); 286 | 287 | SDL_DestroyRenderer(renderer); 288 | SDL_DestroyWindow(window); 289 | SDL_Quit(); 290 | fz_drop_context(ctx); 291 | 292 | return 0; 293 | } 294 | -------------------------------------------------------------------------------- /src/dvi/tex_fontmap.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Frédéric Bour 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include "mydvi.h" 28 | #include "fz_util.h" 29 | 30 | static unsigned long 31 | sdbm_hash(const void *p) 32 | { 33 | unsigned long hash = 0; 34 | int c; 35 | 36 | for (const unsigned char *str = p; (c = *str); str++) 37 | hash = c + (hash << 6) + (hash << 16) - hash; 38 | 39 | return hash * 2654435761; 40 | } 41 | 42 | #define str_hash sdbm_hash 43 | 44 | struct tex_fontmap { 45 | fz_buffer *buffer; 46 | int mask; 47 | tex_fontmap_entry *table; 48 | }; 49 | 50 | tex_fontmap *tex_fontmap_load(fz_context *ctx, fz_stream **streams, int count) 51 | { 52 | fz_ptr(tex_fontmap, result); 53 | fz_ptr(fz_buffer, buffer); 54 | 55 | fz_try(ctx) 56 | { 57 | fz_buffer *buffer = fz_new_buffer(ctx, 1024 * 1024); 58 | for (int i = 0; i < count; ++i) 59 | { 60 | if (!streams[i]) continue; 61 | fz_ptr(fz_stream, leech); 62 | fz_try(ctx) 63 | { 64 | leech = fz_open_leecher(ctx, streams[i], buffer); 65 | while (fz_skip(ctx, leech, 1024 * 1024) > 0); 66 | fz_append_byte(ctx, buffer, '\n'); 67 | } 68 | fz_always(ctx) 69 | { 70 | if (leech) 71 | fz_drop_stream(ctx, leech); 72 | } 73 | fz_catch(ctx) 74 | { 75 | fz_rethrow(ctx); 76 | } 77 | } 78 | fz_append_byte(ctx, buffer, 0); 79 | fz_trim_buffer(ctx, buffer); 80 | 81 | result = fz_malloc_struct(ctx, tex_fontmap); 82 | 83 | char *ptr = (char *)buffer->data; 84 | int count = 0, capacity = 0; 85 | tex_fontmap_entry *rawtable = NULL; 86 | 87 | #define is_ws(c) ((c) == ' ' || (c) == '\t') 88 | #define is_nl(c) ((c) == '\n') 89 | #define is_lt(c) ((c) == '<') 90 | #define is_quote(c) ((c) == '"') 91 | 92 | #define seek(ptr, pred) while (*(ptr) && !is_nl(*(ptr)) && !(pred(*(ptr)))) (ptr)++ 93 | #define fail_if(ptr, pred) if (!*(ptr) || (pred(*ptr))) goto seek_next_line 94 | 95 | while (*ptr) 96 | { 97 | tex_fontmap_entry entry = {0,}; 98 | 99 | seek(ptr, !is_ws); 100 | if (*ptr == '\n') { ptr++; continue; } 101 | if (*ptr == '%') goto seek_next_line; 102 | 103 | entry.pk_font_name = ptr; 104 | seek(ptr, is_ws); fail_if(ptr, is_nl); 105 | char *eol = ptr; 106 | 107 | seek(ptr, !is_ws); 108 | if (!is_lt(*ptr)) 109 | { 110 | *eol = '\0'; 111 | entry.ps_font_name = ptr; 112 | seek(ptr, is_ws); 113 | eol = ptr; 114 | } 115 | seek(ptr, !is_ws); 116 | 117 | while (*ptr && !is_nl(*ptr)) 118 | { 119 | if (is_quote(*ptr)) 120 | { 121 | ptr++; 122 | entry.ps_snippet = ptr; 123 | seek(ptr, is_quote); 124 | fail_if(ptr, !is_quote); 125 | *eol = '\0'; 126 | eol = ptr; 127 | seek(ptr, is_ws); 128 | } 129 | else if (is_lt(*ptr)) 130 | { 131 | ptr++; 132 | seek(ptr, !is_ws); 133 | if (*ptr == '[') ptr++; 134 | seek(ptr, !is_ws); 135 | 136 | char *start = ptr; 137 | seek(ptr, is_ws); 138 | 139 | if (ptr - start >= 4 && 140 | ptr[-4] == '.' && ptr[-3] == 'e' && 141 | ptr[-2] == 'n' && ptr[-1] == 'c') 142 | entry.enc_file_name = start; 143 | else 144 | entry.font_file_name = start; 145 | } 146 | else 147 | goto seek_next_line; 148 | *eol = '\0'; 149 | eol = ptr; 150 | 151 | seek(ptr, !is_ws); 152 | } 153 | 154 | if (is_nl(*ptr)) 155 | { 156 | *eol = '\0'; 157 | ptr++; 158 | 159 | if (capacity == 0) 160 | { 161 | rawtable = fz_malloc_struct_array(ctx, 128, tex_fontmap_entry); 162 | capacity = 128; 163 | } 164 | else if (count >= capacity) 165 | { 166 | tex_fontmap_entry *tmp = fz_malloc_struct_array(ctx, capacity * 2, tex_fontmap_entry); 167 | memcpy(tmp, rawtable, sizeof(tex_fontmap_entry) * capacity); 168 | fz_free(ctx, rawtable); 169 | rawtable = tmp; 170 | capacity *= 2; 171 | } 172 | 173 | //printf("register entry:\n"); 174 | //printf("- pk name: %s\n", entry.pk_font_name); 175 | //printf("- ps name: %s\n", entry.ps_font_name); 176 | //printf("- ps snippet: %s\n", entry.ps_snippet); 177 | //printf("- enc file: %s\n", entry.enc_file_name); 178 | //printf("- font file: %s\n", entry.font_file_name); 179 | entry.hash = str_hash(entry.pk_font_name); 180 | rawtable[count] = entry; 181 | count += 1; 182 | continue; 183 | } 184 | 185 | seek_next_line: 186 | { 187 | char c = *ptr; 188 | *ptr = '\0'; 189 | if (0 && entry.pk_font_name) 190 | { 191 | fprintf(stderr, "skip entry:\n"); 192 | fprintf(stderr, "- pk name: %s\n", entry.pk_font_name); 193 | fprintf(stderr, "- ps name: %s\n", entry.ps_font_name); 194 | fprintf(stderr, "- ps snippet: %s\n", entry.ps_snippet); 195 | fprintf(stderr, "- enc file: %s\n", entry.enc_file_name); 196 | fprintf(stderr, "- font file: %s\n", entry.font_file_name); 197 | } 198 | *ptr = c; 199 | while (*ptr && !is_nl(*ptr)) ptr++; 200 | if (is_nl(*ptr)) ptr++; 201 | } 202 | } 203 | 204 | #undef is_ws 205 | #undef is_nl 206 | #undef is_ltgg 207 | #undef is_quote 208 | #undef seek 209 | #undef fail_if 210 | 211 | if (count + count / 4 > capacity) 212 | capacity *= 2; 213 | 214 | tex_fontmap_entry *hashtable = 215 | fz_malloc_struct_array(ctx, capacity, tex_fontmap_entry); 216 | 217 | int mask = capacity - 1; 218 | for (int i = 0; i < count; ++i) 219 | { 220 | tex_fontmap_entry entry = rawtable[i]; 221 | int index = entry.hash & mask; 222 | while (hashtable[index].pk_font_name) 223 | { 224 | if ((entry.hash & mask) < (hashtable[index].hash & mask)) 225 | { 226 | tex_fontmap_entry tmp = hashtable[index]; 227 | hashtable[index] = entry; 228 | entry = tmp; 229 | } 230 | 231 | index = (index + 1) & mask; 232 | } 233 | hashtable[index] = entry; 234 | } 235 | 236 | result->buffer = buffer; 237 | result->mask = mask; 238 | result->table = hashtable; 239 | fz_free(ctx, rawtable); 240 | } 241 | fz_catch(ctx) 242 | { 243 | if (result) 244 | fz_free(ctx, result); 245 | if (buffer) 246 | fz_drop_buffer(ctx, buffer); 247 | fz_rethrow(ctx); 248 | } 249 | 250 | return result; 251 | } 252 | 253 | void tex_fontmap_free(fz_context *ctx, tex_fontmap *t) 254 | { 255 | fz_free(ctx, t->table); 256 | fz_drop_buffer(ctx, t->buffer); 257 | fz_free(ctx, t); 258 | } 259 | 260 | #ifdef CALC_STATS 261 | int max_poschain = 0, max_negchain = 0, lookup_count = 0, lookup_probe = 0; 262 | 263 | static void print_chain(void) 264 | { 265 | fprintf(stderr, "tex_fontmap: max_pos_chain: %d\n", max_poschain); 266 | fprintf(stderr, "tex_fontmap: max_neg_chain: %d\n", max_negchain); 267 | fprintf(stderr, "tex_fontmap: lookup_count: %d\n", lookup_count); 268 | fprintf(stderr, "tex_fontmap: average_chain: %f\n", (double)lookup_probe / (double)lookup_count); 269 | } 270 | #endif 271 | 272 | tex_fontmap_entry *tex_fontmap_lookup(tex_fontmap *t, const char *name) 273 | { 274 | #ifdef CALC_STATS 275 | static int v = 0; 276 | if (!v) 277 | { 278 | v = 1; 279 | atexit(print_chain); 280 | } 281 | lookup_count += 1; 282 | int chainlen = 0; 283 | #endif 284 | 285 | 286 | unsigned long hash = str_hash(name); 287 | int index = hash & t->mask; 288 | 289 | while (t->table[index].pk_font_name) 290 | { 291 | #ifdef CALC_STATS 292 | lookup_probe += 1; 293 | chainlen += 1; 294 | #endif 295 | if (t->table[index].hash == hash && 296 | !strcmp(t->table[index].pk_font_name, name)) 297 | { 298 | #ifdef CALC_STATS 299 | if (chainlen > max_poschain) 300 | max_poschain = chainlen; 301 | #endif 302 | return &t->table[index]; 303 | } 304 | 305 | index = (index + 1) & t->mask; 306 | } 307 | 308 | #ifdef CALC_STATS 309 | if (chainlen > max_negchain) 310 | max_negchain = chainlen; 311 | #endif 312 | return NULL; 313 | } 314 | 315 | tex_fontmap_entry *tex_fontmap_iter(tex_fontmap *t, unsigned *index) 316 | { 317 | while (*index <= t->mask) 318 | { 319 | unsigned i = *index; 320 | *index += 1; 321 | if (t->table[i].pk_font_name) 322 | return &t->table[i]; 323 | } 324 | return NULL; 325 | } 326 | -------------------------------------------------------------------------------- /EDITOR-PROTOCOL.md: -------------------------------------------------------------------------------- 1 | # Controlling TeXpresso from an editor 2 | 3 | The process should be started from the editor passing the root TeX file as argument: 4 | 5 | ``` 6 | texpressso [-I path]* [-json] [-lines] /root.tex 7 | ``` 8 | 9 | The rest of the communication will happen on stdin/stdout: 10 | - TeXpresso reads and interprets commands found on stdin 11 | - when it needs to communicate some events, it writes a message on stdout. 12 | 13 | Description of the arguments: 14 | - `-json`: use a JSON syntax rather than SEXP syntax for communication 15 | - `-lines`: update output buffers line-by-line rather than by chunks of bytes (using `append-lines`/`truncate-lines` rather than `append`/`truncate` messages) 16 | - `-I path`: populate an "include path" in which files should be looked up in priority 17 | 18 | The include path is useful if one uses a build system that puts auxiliary files in a dedicated build directory, while the TeX sources are in a separate source directory. In this case, TeXpresso can be started using `texpresso -I build/ source/main.tex`. 19 | 20 | ### General considerations 21 | 22 | ### Best effort and asynchronous protocol 23 | 24 | It is not a query/answer protocol (e.g. request something on stdin, read the answer from stdout): stdin and stdout are processed independently. 25 | 26 | The editor should just write a line on stdin when it needs to communicate something to TeXpresso. 27 | 28 | Conversely, it can interpret the messages it is interested in; ignoring the others should be safe. 29 | 30 | ### Syntax 31 | 32 | The protocol is designed around s-expressions (each command and message is represented by one s-exp value). 33 | 34 | JSON-mode (`-json` flag) prints sexp as follow: 35 | - a sexp-list is represented by a JSON array 36 | - a sexp atom (symbol) is represented by a JSON string 37 | - special characters are escaped using JSON lexical conventions 38 | 39 | ### VFS 40 | 41 | An important part of the protocol is communicating the contents of a "virtual file system" to TeXpresso. This "VFS" is made of the buffers opened in the editor, which contents might have not been saved to disk yet. 42 | When looking for a file, TeXpresso checks in its VFS first and then fallbacks to disk. The editor should try to keep TeXpresso view synchronized. Emacs mode does this by listening for changes and sending deltas to TeXpresso. 43 | 44 | ### Strings, codepoints and bytes 45 | 46 | LaTeX consumes files as a stream of bytes (8-bit integers). TeXpresso makes no assumption on the encoding, it just shares the raw contents stored on disk. 47 | 48 | However, this might not be the case for the content communicated by the editor. 49 | JSON strings, for instance, are serialized sequences of unicode codepoints. TeXpresso will convert it to an UTF-8 encoded byte string before sharing with LaTeX. 50 | Offsets specified in byte-based delta commands (`change`) should therefore refer to byte offsets of the UTF-8 representation. 51 | Alternatively, one can use line-based communication (using `change-lines` for editor->TeXpresso communication, and by passing `-lines` argument for TeXpresso->editor messages). Lines are numbered by counting '\n'. 52 | 53 | ## Commands (editor -> texpresso) 54 | 55 | ```scheme 56 | (open "path" "contents") 57 | ``` 58 | 59 | Populate TeXpresso VFS with a file at "path" storing "contents". 60 | 61 | 62 | ```scheme 63 | (close "path") 64 | ``` 65 | 66 | Remove file at "path" from TeXpresso VFS. 67 | 68 | ```scheme 69 | (change "path" offset length "data") 70 | ``` 71 | 72 | Update file at "path" in VFS (it should have been `open`ed before), by replacing `length` bytes starting at `offset` (both are integers) with the contents of "data". 73 | 74 | ```scheme 75 | (change-lines "path" offset count "data") 76 | ``` 77 | 78 | Update file at "path" in VFS (it should have been `open`ed before), by replacing `length` lines starting from line number `offset` (both are integers) with the contents of "data". 79 | Line numbering starts at 0 and line count is defined as the number of '\n'. 80 | To insert multiple lines, separate them with '\n' in data. Note that if data ends with '\n', it will insert a new empty-line at the end. 81 | 82 | ```scheme 83 | (change-range "path" start-line start-column end-line end-column "replacement-text") 84 | ``` 85 | 86 | Update file at "path" in VFS (it should have been `open`ed before), by replacing the characters in range starting from line `start-line` at column `start-column` (implemented by counting the number of UTF-16 code units, with 0 being the beginning of the line) up to line `end-line` at column `end-column`. This is designed to be compatible with [LSP position encoding](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#positionEncodingKind) using the default 'utf-16' encoding. 87 | 88 | ```scheme 89 | (theme (bg_r bg_g bg_b) (fg_r fg_g fg_b)) 90 | ``` 91 | 92 | Synchronize the theme with the one of the editor, to display the document with the same background and foreground colors. 93 | Values `bg_r`, `bg_g`, ... are floating point values in `[0.0, 1.0]` interval. 94 | 95 | ```scheme 96 | (previous-page) 97 | (next-page) 98 | ``` 99 | 100 | Display previous or the next page of the document. It is convenient to bind these to some shortcuts to change pages without leaving the editor (in Emacs this is done via the `(texpresso-{previous,next}-page)` interactive commands). 101 | 102 | ```scheme 103 | (move-window x y w h) 104 | ``` 105 | 106 | Move TeXpresso to the specified screen coordinates. For GUI editors, this can be convenient to keep TeXpresso positioned relative to the main window. 107 | 108 | ```scheme 109 | (rescan) 110 | ``` 111 | 112 | Check the filesystem for changes. This will reload and reprocess any changed file. 113 | 114 | ```scheme 115 | (stay-on-top t) 116 | (stay-on-top nil) 117 | ``` 118 | 119 | 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). 120 | 121 | ```scheme 122 | (synctex-forward "path" line) 123 | ``` 124 | 125 | Try to scroll the UI to the contents defined in TeX file at "path" and line. The path can be absolute or relative to the root document. 126 | 127 | ## Messages (texpresso -> editor) 128 | 129 | ### Byte-based synchronization of output messages and log file 130 | 131 | ``` 132 | (truncate out|log size) 133 | (append out|log text) 134 | (flush) 135 | ``` 136 | 137 | These three commands are used to share the contents of LaTeX output messages and logs in real-time. 138 | During a LaTeX run, these are append-only emitting only `append` commands. When a change happens in the document, the process might backtrack, sending a `truncate` message to drop the invalidated parts of the output. 139 | Size is expressed as a number of byte. 140 | 141 | Because this can happen a lot during edition, it is a good practice to buffer these changes and only reflect from time to time. TeXpresso will signal moments it deemed appropriate with a `flush` message. 142 | Sample script: 143 | ``` 144 | (truncate out 0) 145 | (truncate log 0) 146 | (append out "foo.tex:12: overfull hbox") 147 | (append log "This is XeTeX...") 148 | (flush) 149 | ``` 150 | 151 | ### Line-based synchronization of output messages and log file 152 | 153 | ``` 154 | (truncate-lines out|log count) 155 | (append-lines out|log line1 line2... lineN) 156 | (flush) 157 | ``` 158 | 159 | Like their byte-based counterparts, these commands are used to share the contents of LaTeX output messages and logs in real-time. They are used instead of the byte-based one when TeXpresso is started with `-lines` flag, 160 | 161 | As the LaTeX process runs, it appends data to the standard output and to the log file. 162 | All completed lines (separated by `\n`), TeXpresso communicates them to the editor. The `\n` are not included in the serialized strings. 163 | 164 | When a change happens in the document, the process might backtrack, sending a `truncate-lines` message to drop the invalidated parts of the output. Only `count` lines should be kept. 165 | 166 | Because this can happen a lot during edition, it is a good practice to buffer these changes and only reflect from time to time. TeXpresso will signal moments it deemed appropriate with a `flush` message. This is less of a problem with the line-based output. 167 | 168 | For instance, to avoid flickering, an editor can keep stale lines after receiving a `truncate-lines` message, overwrite them as `append-lines` messages are received, and really truncate when receiving `flush` message. At this point, all invalidated contents as been removed but the transition has been smoothed out. 169 | 170 | ### SyncTeX 171 | 172 | ``` 173 | (synctex "path" line) 174 | ``` 175 | 176 | SyncTeX backward synchronisation: the user clicked on text produced by LaTeX sources at path:line. The action is usually to open this file in the editor and jumps to this line. 177 | 178 | ### VFS reset 179 | 180 | ``` 181 | (reset-sync) 182 | ``` 183 | 184 | Output by TeXpresso when the contents of its VFS has been lost. The editor should re-`open` any file before sharing `change`s. 185 | Not urgent: this notification is used mainly when debugging TeXpresso, it should not happen during normal use. 186 | 187 | ### Files used by the document 188 | 189 | ``` 190 | (input-file index "path") 191 | ``` 192 | 193 | Output by TeXpresso when a new source file is read by the document. 194 | 195 | During a single run `index` is a unique, monotonous integer. When a new `input-file` message is produced with a lower index, it means that the process backtracked and all files with a higher index are no longer monitored. (Though this is very likely to be temporary, it is better to batch the changes and update the editor state only from time to time). 196 | 197 | The paths are printed relative to the root file. They might be non-existent on the file-system (for instance if they exist only in TeXpresso's overlay, that is the case for the intermediate files produced by beamer), so editor plugins should not make any assumptions and validate the path themselves. 198 | 199 | Right now, this is implemented by hooking into SyncTeX: 200 | - only text files are tracked (not graphics) 201 | - the indices printed are the SyncTex input indices; they should be attributed no other meaning than being monotonic and useful to detect backtracking occurrences 202 | --------------------------------------------------------------------------------