Hendri Karisma

← Blog← Blog

WritePaperTeX: compiling LaTeX on a tablet, with no network at allWritePaperTeX: mengompilasi LaTeX di tablet, tanpa jaringan sama sekali

2026-09-29

WritePaperTeX launcher icon: a sheet of paper with a folded corner and a serif X

There is no TeX Live for Android. No pdflatex to call, and a full TeX distribution is measured in gigabytes. Which is why everyone writes in Overleaf — and why writing on a plane, or in a room with bad Wi-Fi, quietly stops being possible.

WritePaperTeX is a LaTeX editor that compiles on the tablet itself. No compile server, no account, no network. It is the companion to ReadPaper: one reads papers, this one writes them.

A TeX engine inside the APK

The compiler is Tectonic — XeTeX rebuilt around Rust — compiled to a shared library and called from Dart through dart:ffi, running in an isolate. On Linux the app calls latexmk and XeLaTeX instead, because a machine that already has TeX Live does not need a second copy of it.

Tectonic normally downloads packages on demand. On a tablet with no signal that fails, and it fails dishonestly: the error reads failed to open input file hyph-en-us.tex, which never mentions the network at all. So the packages ship inside the APK — 793 files, 14.8 MB compressed. The APK is 85 MB, and 36 MB of that is ICU data alone.

The numbers that matter. First compile on a fresh install: 3.7 seconds. A real dissertation proposal, with Wi-Fi switched off: a full pass in 1 minute 1 second, a quick pass in 8.7 seconds, with no downloads at all.

Two days of being wrong about how hard it was

The first attempt at building Tectonic for Android died at pkg-config has not been configured to support cross-compilation. Tectonic is not pure Rust — it wraps XeTeX and wants seven C libraries: fontconfig, freetype2, graphite2, ICU, libpng, harfbuzz, flate. I wrote in my notes that this was days of work, not an afternoon.

It took one more day. The missing piece was a single environment variable, TECTONIC_DEP_BACKEND=vcpkg: vcpkg has an arm64-android triplet and builds all seven itself. My notes now carry the correction, which I left in rather than quietly editing out. An estimate that wrong is worth keeping visible.

Underneath were four smaller traps that only show up once you try:

  • fontconfig needs gperf present at build time;
  • the build must come from Tectonic's own repository at a release tag, not from crates.io, or cargo pulls sub-crates that no longer agree with each other;
  • ICU 78's headers require C++17, while Tectonic 0.15 still passes -std=c++14;
  • a pinned version of the time crate no longer compiles under current rustc.

The bugs that only exist on a device

The app died instead of reporting an error. The first compile on the tablet ended in SIGABRT inside the compile call. Rust was set to abort on panic, so a panic anywhere inside Tectonic took the whole application with it. It unwinds now, so a panic becomes a message.

The font that was missing because I had it. The bundle is assembled on my laptop, where TeX Gyre Termes is installed — so fontconfig found it locally and it never got downloaded into the bundle. On the tablet: The font texgyretermes-regular cannot be found. The fix was to have the bundling document ask for fonts by filename, which forces the lookup through TeX, which means through the bundle.

Every HTTPS connection refused. "The SSL certificate is invalid", for all of them. Android names the files in its certificate store using an older hash format than modern OpenSSL looks for. The certificates were all present; nothing could find them. They are now copied into a single PEM file once, and that file is what gets pointed at.

A timeout that measures being stuck, not being slow

A first compile of a dissertation can genuinely take ten minutes. A timeout that cuts at five kills correct work just before it finishes. So the limit is on silence: three minutes with no progress, not three minutes in total. What deserves suspicion is the app going quiet, not the app taking a while.

Which requires progress to exist in the first place. Tectonic says nothing until it is done. So the Rust side writes each step to a file and flushes it, and Dart reads that file every 400 ms while it waits. A progress bar that sits still for several minutes is indistinguishable from an app that has hung.

Build output belongs outside the project

Every .aux, .log and .fls goes into .writepapertex/build/, and that folder carries a .gitignore that ignores itself — so the author's own .gitignore is never touched. There is a test for it.

That has a consequence worth following through: because the output PDF lives in an ignored folder, it would never be pushed, and someone opening the repository without TeX installed would find nothing readable. So the compiled PDF is copied back into the project folder on purpose.

Git on a tablet, and a decision that changed

ReadPaper does git over the GitHub REST API, because Android has no git binary. WritePaperTeX deliberately does not reuse that. Its scope is different: it has to work with GitHub, GitLab, and a self-hosted Gitea — and writing one adaptor per provider turns every new provider into work forever.

The plan was to implement git's smart HTTP protocol directly. The code does not do that. Writing the protocol yourself is months of work that other people have already done correctly, so it vendors libgit2 instead. I am leaving the original plan in the notes next to what actually happened, because a decision reversed by contact with reality is more useful to read than a decision that was right the first time.

SSH is not supported on purpose: a private key on a tablet raises a storage problem larger than the convenience is worth, and tokens can be revoked one at a time.

Smaller decisions, stated

XeLaTeX, not pdfLaTeX. XeLaTeX reads UTF-8 and system fonts without ceremony, which matters when you write Indonesian. Tectonic is XeTeX-based too, so the desktop engine and the mobile engine behave the same way. The two choices lock together.

The main file is detected from \documentclass, not from its name. Guessing by name picks main.tex even in a project whose entry point is skripsi.tex.

No flutter_rust_bridge. Raw dart:ffi instead: only one function is needed, and that much code generation for one function only adds parts that can break.

Autocomplete reads TeXstudio and Kile's CWL format, loaded according to the document's own \usepackage lines, with nine files bundled. Dangling \refs are reported with line numbers.

No live collaboration. That needs a server, and this is a local editor. Collaborating through git is enough.

Where it stands

  • The APK is signed with a debug key, so Android warns about an unknown source on install.
  • The interface is Indonesian only for now.
  • No search and replace, no usable undo, no line numbers yet.
  • SyncTeX files are produced, but tapping to jump between source and PDF is not wired up.
  • Android and Linux only. Windows is mentioned in the README and has no code behind it.
  • There are no screenshots yet. The editor is a three-pane tablet layout: file tree, editor, PDF preview.

Get it

AGPL-3.0-or-later, same as ReadPaper. Use it, change it, sell it — but anyone you hand a copy to, including over a network, has the right to the source under the same terms. If a compile service ever runs as a server, that licence is what keeps its source open too. Contributions take a git commit -s sign-off and no CLA.

Linux desktop builds from source and needs texlive-xetex and latexmk installed.

Ikon WritePaperTeX: selembar kertas dengan sudut terlipat dan huruf X berserif

Tidak ada TeX Live untuk Android. Tidak ada pdflatex untuk dipanggil, dan distribusi TeX yang lengkap diukur dalam gigabita. Karena itulah semua orang menulis di Overleaf — dan karena itu pula menulis di pesawat, atau di ruangan dengan Wi-Fi buruk, diam-diam berhenti menjadi mungkin.

WritePaperTeX adalah penyunting LaTeX yang mengompilasi di tabletnya sendiri. Tanpa server kompilasi, tanpa akun, tanpa jaringan. Ia pasangan ReadPaper: yang satu membaca paper, yang ini menulisnya.

Mesin TeX di dalam APK

Kompilatornya Tectonic — XeTeX yang dibangun ulang di sekitar Rust — dikompilasi jadi pustaka bersama dan dipanggil dari Dart lewat dart:ffi, berjalan di isolate. Di Linux aplikasinya memanggil latexmk dan XeLaTeX saja, karena mesin yang sudah punya TeX Live tidak butuh salinan keduanya.

Tectonic biasanya mengunduh paket sesuai kebutuhan. Di tablet tanpa sinyal itu gagal, dan gagalnya tidak jujur: pesannya berbunyi failed to open input file hyph-en-us.tex, yang sama sekali tidak menyebut jaringan. Jadi paketnya ikut dibungkus di dalam APK — 793 berkas, 14,8 MB terkompresi. APK-nya 85 MB, dan 36 MB di antaranya hanya data ICU.

Angka yang penting. Kompilasi pertama pada pemasangan baru: 3,7 detik. Proposal disertasi sungguhan, dengan Wi-Fi dimatikan: lintasan penuh 1 menit 1 detik, lintasan cepat 8,7 detik, tanpa satu pun unduhan.

Dua hari keliru menaksir kesulitannya

Percobaan pertama membangun Tectonic untuk Android mati di pkg-config has not been configured to support cross-compilation. Tectonic bukan Rust murni — ia membungkus XeTeX dan menuntut tujuh pustaka C: fontconfig, freetype2, graphite2, ICU, libpng, harfbuzz, flate. Di catatan saya waktu itu tertulis: ini pekerjaan berhari-hari, bukan satu sore.

Ternyata cukup sehari lagi. Yang terlewat hanya satu variabel lingkungan, TECTONIC_DEP_BACKEND=vcpkg: vcpkg punya triplet arm64-android dan membangun ketujuhnya sendiri. Catatan saya sekarang memuat koreksinya, yang sengaja saya biarkan tertulis alih-alih diam-diam dihapus. Taksiran sesalah itu layak tetap terlihat.

Di bawahnya ada empat jebakan kecil yang baru terlihat setelah dicoba:

  • fontconfig menuntut gperf tersedia saat build;
  • build harus dari repositori Tectonic sendiri pada tag rilis, bukan dari crates.io, kalau tidak cargo menarik sub-crate yang sudah tidak sejalan;
  • header ICU 78 menuntut C++17, sementara Tectonic 0.15 masih menulis -std=c++14;
  • versi crate time yang terkunci tidak lagi terkompilasi dengan rustc sekarang.

Bug yang hanya ada di perangkat

Aplikasinya mati alih-alih melaporkan kesalahan. Kompilasi pertama di tablet berakhir dengan SIGABRT di dalam panggilan kompilasinya. Rust disetel untuk abort saat panic, jadi satu panic di mana pun di dalam Tectonic membawa serta seluruh aplikasi. Sekarang ia unwind, sehingga panic menjadi pesan.

Font yang hilang justru karena saya memilikinya. Bundelnya disusun di laptop saya, tempat TeX Gyre Termes sudah terpasang — jadi fontconfig menemukannya secara lokal dan font itu tidak pernah ikut terunduh ke dalam bundel. Di tablet: The font texgyretermes-regular cannot be found. Perbaikannya: dokumen penyusun bundel meminta font lewat nama berkas, yang memaksa pencariannya lewat TeX, yang berarti lewat bundel.

Setiap sambungan HTTPS ditolak. "The SSL certificate is invalid", semuanya. Android menamai berkas di penyimpanan sertifikatnya dengan format hash yang lebih tua daripada yang dicari OpenSSL modern. Sertifikatnya ada semua; tidak ada yang bisa menemukannya. Sekarang sertifikat itu disalin jadi satu berkas PEM sekali, lalu berkas itu yang ditunjuk.

Batas waktu yang mengukur kemacetan, bukan kelambatan

Kompilasi pertama sebuah disertasi memang bisa makan sepuluh menit. Batas waktu yang memotong di menit kelima membunuh pekerjaan yang benar tepat sebelum selesai. Jadi yang dibatasi adalah diamnya: tiga menit tanpa kemajuan, bukan tiga menit total. Yang pantas dicurigai adalah aplikasinya membisu, bukan aplikasinya butuh waktu.

Dan itu menuntut adanya kemajuan yang bisa dilihat. Tectonic tidak berkata apa pun sampai selesai. Jadi sisi Rust menulis tiap langkah ke sebuah berkas lalu menyiramnya, dan Dart membaca berkas itu tiap 400 milidetik sambil menunggu. Bilah kemajuan yang diam selama beberapa menit tidak bisa dibedakan dari aplikasi yang menggantung.

Keluaran build tempatnya di luar proyek

Setiap .aux, .log, dan .fls masuk ke .writepapertex/build/, dan folder itu membawa .gitignore yang mengabaikan dirinya sendiri — jadi .gitignore milik penulisnya tidak pernah disentuh. Ada tesnya.

Ada akibat yang perlu ditindaklanjuti: karena PDF hasilnya berada di folder yang diabaikan, ia tidak akan pernah ikut ter-push, dan orang yang membuka repositorinya tanpa TeX terpasang tidak menemukan apa pun yang bisa dibaca. Jadi PDF hasil kompilasi sengaja disalin balik ke folder proyek.

Git di tablet, dan satu keputusan yang berubah

ReadPaper menjalankan git lewat GitHub REST API, karena Android tidak punya biner git. WritePaperTeX sengaja tidak memakai ulang jalan itu. Ruang lingkupnya berbeda: ia harus jalan dengan GitHub, GitLab, dan Gitea yang dipasang sendiri — dan menulis satu adaptor per penyedia membuat setiap penyedia baru menjadi pekerjaan selamanya.

Rencananya menulis sendiri protokol smart HTTP milik git. Kodenya tidak melakukan itu. Menulis protokolnya sendiri adalah pekerjaan berbulan yang sudah diselesaikan orang lain dengan benar, jadi yang dipakai libgit2, di-vendor. Rencana awalnya saya biarkan tertulis di catatan berdampingan dengan apa yang akhirnya terjadi, karena keputusan yang dibalik oleh benturan dengan kenyataan lebih berguna dibaca daripada keputusan yang benar sejak awal.

SSH sengaja tidak didukung: kunci privat di tablet menimbulkan persoalan penyimpanan yang lebih besar daripada kemudahannya, dan token bisa dicabut satu per satu.

Keputusan-keputusan kecil, disebutkan

XeLaTeX, bukan pdfLaTeX. XeLaTeX membaca UTF-8 dan font sistem tanpa upacara, dan itu penting kalau yang ditulis berbahasa Indonesia. Tectonic pun berbasis XeTeX, jadi mesin desktop dan mesin mobile berperilaku sama. Kedua pilihan itu saling mengunci.

Berkas utama dideteksi dari \documentclass, bukan dari namanya. Menebak dari nama akan memilih main.tex bahkan pada proyek yang titik masuknya skripsi.tex.

Tanpa flutter_rust_bridge. Yang dipakai dart:ffi mentah: yang dibutuhkan hanya satu fungsi, dan codegen sebesar itu untuk satu fungsi hanya menambah bagian yang bisa rusak.

Pelengkapan otomatis membaca format CWL milik TeXstudio dan Kile, dimuat mengikuti baris \usepackage dokumennya sendiri, dengan sembilan berkas ikut dibundel. \ref yang menggantung dilaporkan lengkap dengan nomor barisnya.

Tanpa kolaborasi serentak. Itu butuh server, dan ini penyunting lokal. Berkolaborasi lewat git sudah cukup.

Keadaannya sekarang

  • APK-nya ditandatangani kunci debug, jadi Android memperingatkan soal sumber tidak dikenal saat memasang.
  • Antarmukanya masih bahasa Indonesia saja.
  • Belum ada cari-dan-ganti, belum ada undo yang layak, belum ada nomor baris.
  • Berkas SyncTeX sudah dihasilkan, tapi ketukan untuk melompat antara sumber dan PDF belum disambungkan.
  • Baru Android dan Linux. Windows disebut di README dan belum ada kodenya.
  • Belum ada tangkapan layar. Penyuntingnya bertata letak tiga panel untuk tablet: pohon berkas, editor, pratinjau PDF.

Mengambilnya

AGPL-3.0-or-later, sama seperti ReadPaper. Silakan dipakai, diubah, dijual — tetapi siapa pun yang menerima salinannya, termasuk lewat jaringan, berhak atas sumbernya dengan syarat yang sama. Kalau suatu saat ada layanan kompilasi yang dijalankan sebagai server, lisensi itulah yang membuat sumbernya tetap terbuka. Kontribusi memakai tanda tangan git commit -s, tanpa CLA.

Untuk Linux desktop, dibangun dari sumbernya dan butuh texlive-xetex serta latexmk terpasang.