Ely Office: six offline-first desktop apps in Rust and GPUI
ElyDoc, ElyTable, ElyShow (LibreOfficeKit), ElyPDF (MuPDF), ElyTree (Git), ElyMark (Milkdown and CodeMirror in CEF). ElyMail and ElyAgent follow.
This commit is contained in:
817 files changed
+143101
No files matched your search
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
# Pinned libetpan has every header MailCore 2 includes.
|
||||
set -euo pipefail
|
||||
|
||||
root=$(cd "$(dirname "$0")/../.." && pwd)
|
||||
prefix=$root/engines/libetpan
|
||||
work=$(mktemp -d)
|
||||
trap 'rm -rf "$work"' EXIT
|
||||
fail() {
|
||||
echo "FAIL: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
"$root/scripts/build-libetpan.sh" >"$work/build.log" 2>&1 || {
|
||||
cat "$work/build.log" >&2
|
||||
fail "build failed"
|
||||
}
|
||||
[ -s "$prefix/lib/libetpan.a" ] || fail "no libetpan.a"
|
||||
config=$prefix/BUILD-config.h
|
||||
grep -q '^#define HAVE_OPENSSL 1' "$config" || fail "libetpan built without TLS"
|
||||
grep -q '^#define HAVE_JSON 1' "$config" || fail "libetpan built without json-c"
|
||||
|
||||
headers=$(grep -rhoE '#[[:space:]]*include[[:space:]]*<libetpan/[A-Za-z_0-9]+\.h>' "$root/reference/mailcore2/src" |
|
||||
sed -E 's/.*<(libetpan\/[^>]+)>.*/\1/' | sort -u)
|
||||
[ -n "$headers" ] || fail "MailCore 2 sources include no libetpan header"
|
||||
for header in $headers; do
|
||||
[ -f "$prefix/include/$header" ] || fail "pinned libetpan lacks $header"
|
||||
done
|
||||
echo "libetpan: $(wc -l <<<"$headers" | tr -d ' ') headers MailCore 2 includes are present"
|
||||
Executable
+88
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env bash
|
||||
# Probes LOK on macOS: load, export, tile painting, thread rule.
|
||||
set -euo pipefail
|
||||
|
||||
root=$(cd "$(dirname "$0")/../.." && pwd)
|
||||
here=$root/tests/engines
|
||||
expect=${ELY_LOK_EXPECT_TILE:-untouched}
|
||||
frameworks=${ELY_LOK_DIR:-}
|
||||
work=$(mktemp -d)
|
||||
mount=
|
||||
cleanup() {
|
||||
if [ -n "$mount" ]; then
|
||||
for _ in 1 2 3 4 5 6 7 8 9 10; do
|
||||
if hdiutil detach "$mount" -quiet 2>/dev/null; then
|
||||
mount=
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
[ -z "$mount" ] || {
|
||||
echo "FAIL: cannot detach $mount; left in place" >&2
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
rm -rf "$work"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
fail() {
|
||||
echo "FAIL: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
run() { SAL_FORCE_HIDPI_SCALING=1 SAL_SKIA=raster perl -e 'alarm 180; exec @ARGV' "$@"; }
|
||||
|
||||
case $expect in written | untouched) ;; *) fail "ELY_LOK_EXPECT_TILE is written or untouched, not $expect" ;; esac
|
||||
[ "$(uname)" = Darwin ] || {
|
||||
echo "SKIP lok: macOS only"
|
||||
exit 0
|
||||
}
|
||||
if [ -z "$frameworks" ]; then
|
||||
dmg=$(ls "$root"/engines/LibreOffice_*_MacOS_aarch64.dmg 2>/dev/null | head -n 1 || true)
|
||||
[ -n "$dmg" ] || {
|
||||
echo "SKIP lok: set ELY_LOK_DIR, or put the LibreOffice dmg in engines/"
|
||||
exit 0
|
||||
}
|
||||
[ "$(stat -f %z "$dmg")" = 297798926 ] || fail "$dmg is not the pinned 26.2.6 aarch64 image"
|
||||
mount=$work/mnt
|
||||
mkdir "$mount"
|
||||
hdiutil attach -nobrowse -readonly -mountpoint "$mount" "$dmg" >/dev/null
|
||||
frameworks=$mount/LibreOffice.app/Contents/Frameworks
|
||||
fi
|
||||
|
||||
exports=$(nm -gU "$frameworks/libmergedlo.dylib")
|
||||
for symbol in _libreofficekit_hook _libreofficekit_hook_2 _lok_preinit; do
|
||||
grep -qE " T $symbol\$" <<<"$exports" || fail "libmergedlo does not export $symbol"
|
||||
done
|
||||
plugins=$(ls "$frameworks" | grep '^libvclplug_' || true)
|
||||
[ "$plugins" = libvclplug_osxlo.dylib ] || fail "VCL plugins are not only osx: $plugins"
|
||||
|
||||
python3 - "$work/hello.docx" <<'PY'
|
||||
import sys, zipfile
|
||||
types = '<?xml version="1.0" encoding="UTF-8"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/></Types>'
|
||||
rels = '<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/></Relationships>'
|
||||
body = '<?xml version="1.0" encoding="UTF-8"?><w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body><w:p><w:r><w:rPr><w:b/><w:sz w:val="48"/></w:rPr><w:t>Ely Office 合同 Contract</w:t></w:r></w:p><w:p><w:r><w:t>The quick brown fox jumps over the lazy dog.</w:t></w:r></w:p></w:body></w:document>'
|
||||
with zipfile.ZipFile(sys.argv[1], "w", zipfile.ZIP_DEFLATED) as z:
|
||||
z.writestr("[Content_Types].xml", types)
|
||||
z.writestr("_rels/.rels", rels)
|
||||
z.writestr("word/document.xml", body)
|
||||
PY
|
||||
clang -O1 -I "$root/reference/libreoffice-core/include" "$here/lok_probe.c" -o "$work/probe" || fail "probe does not compile"
|
||||
args=("$frameworks" "file://$work/profile" "file://$work/hello.docx" "file://$work/out.pdf")
|
||||
|
||||
out=$(run "$work/probe" "${args[@]}" unipoll 2>"$work/err") || fail "unipoll probe: $(tail -n 5 "$work/err")"
|
||||
grep -q '"BuildId": "8221e31b3ac356a1623c672912a3d2b492f7e3d1"' <<<"$out" || fail "not the pinned build: $out"
|
||||
grep -qE '^twips=[1-9][0-9]*x[1-9][0-9]*$' <<<"$out" || fail "document has no size: $out"
|
||||
grep -qx 'pdf=1' <<<"$out" || fail "PDF export: $out"
|
||||
[ -s "$work/out.pdf" ] || fail "no PDF written"
|
||||
grep -qx 'polls=0' <<<"$out" || fail "Aqua called the poll callback: $out"
|
||||
case $expect in
|
||||
untouched) grep -qx 'painted=0' <<<"$out" || fail "tile painted, expected untouched: $out" ;;
|
||||
written)
|
||||
grep -qE '^painted=[1-9]' <<<"$out" || fail "tile untouched, expected painted: $out"
|
||||
grep -qE '^ink=[1-9]' <<<"$out" || fail "tile has no text pixels: $out"
|
||||
;;
|
||||
esac
|
||||
|
||||
if run "$work/probe" "${args[@]}" threaded >/dev/null 2>"$work/err"; then fail "threaded mode did not crash"; fi
|
||||
grep -q 'NSWindow should only be instantiated on the main thread' "$work/err" || fail "threaded crash has another cause: $(head -n 3 "$work/err")"
|
||||
echo "lok: load, PDF export, thread rule pass; tile $expect"
|
||||
@@ -0,0 +1,79 @@
|
||||
#define LOK_USE_UNSTABLE_API
|
||||
#include <LibreOfficeKit/LibreOfficeKitInit.h>
|
||||
#include <dispatch/dispatch.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define PX 256
|
||||
#define SENTINEL 0xAB
|
||||
|
||||
static LibreOfficeKit *lok;
|
||||
static const char *doc_url, *pdf_url;
|
||||
static volatile int polls;
|
||||
|
||||
static void probe(void) {
|
||||
printf("version=%s\n", lok->pClass->getVersionInfo(lok));
|
||||
LibreOfficeKitDocument *doc = lok->pClass->documentLoad(lok, doc_url);
|
||||
if (!doc) {
|
||||
printf("load=fail %s\n", lok->pClass->getError(lok));
|
||||
exit(1);
|
||||
}
|
||||
doc->pClass->initializeForRendering(doc, NULL);
|
||||
long w = 0, h = 0;
|
||||
doc->pClass->getDocumentSize(doc, &w, &h);
|
||||
printf("twips=%ldx%ld\npolls=%d\n", w, h, polls);
|
||||
|
||||
unsigned char *tile = malloc(PX * PX * 4);
|
||||
memset(tile, SENTINEL, PX * PX * 4);
|
||||
doc->pClass->paintTile(doc, tile, PX, PX, 0, 0, PX * 15, PX * 15);
|
||||
long painted = 0, ink = 0;
|
||||
for (long i = 0; i < PX * PX; i++) {
|
||||
unsigned char *p = tile + i * 4;
|
||||
if (p[0] != SENTINEL || p[1] != SENTINEL || p[2] != SENTINEL || p[3] != SENTINEL) painted++;
|
||||
if (p[3] > 0 && p[0] < 128 && p[1] < 128 && p[2] < 128) ink++;
|
||||
}
|
||||
printf("painted=%ld\nink=%ld\npdf=%d\n", painted, ink, doc->pClass->saveAs(doc, pdf_url, "pdf", NULL));
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
static int poll_cb(void *data, int timeout_us) {
|
||||
polls++;
|
||||
return 0;
|
||||
}
|
||||
static void wake_cb(void *data) {}
|
||||
|
||||
static void *post_probe(void *arg) {
|
||||
sleep(1);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
probe();
|
||||
_exit(0);
|
||||
});
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc != 6) {
|
||||
fprintf(stderr, "usage: install profile-url doc-url pdf-url unipoll|threaded\n");
|
||||
return 2;
|
||||
}
|
||||
doc_url = argv[3];
|
||||
pdf_url = argv[4];
|
||||
int unipoll = strcmp(argv[5], "unipoll") == 0;
|
||||
if (unipoll) setenv("SAL_LOK_OPTIONS", "unipoll", 1);
|
||||
lok = lok_init_2(argv[1], argv[2]);
|
||||
if (!lok) {
|
||||
fprintf(stderr, "lok_init_2 failed\n");
|
||||
return 1;
|
||||
}
|
||||
if (!unipoll) {
|
||||
probe();
|
||||
return 0;
|
||||
}
|
||||
pthread_t thread;
|
||||
pthread_create(&thread, NULL, post_probe, NULL);
|
||||
lok->pClass->runLoop(lok, poll_cb, wake_cb, NULL);
|
||||
return 0;
|
||||
}
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
# Builds MuPDF, renders a PDF page, extracts its text.
|
||||
set -euo pipefail
|
||||
|
||||
root=$(cd "$(dirname "$0")/../.." && pwd)
|
||||
out=$root/engines/mupdf-build
|
||||
work=$(mktemp -d)
|
||||
trap 'rm -rf "$work"' EXIT
|
||||
fail() {
|
||||
echo "FAIL: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
"$root/scripts/build-mupdf.sh" >"$work/build.log" 2>&1 || fail "build failed, see $work/build.log"
|
||||
python3 - "$work/hello.pdf" <<'PY'
|
||||
import sys
|
||||
stream = b"BT /F1 24 Tf 72 700 Td (Ely Office contract) Tj ET"
|
||||
objs = [
|
||||
b"<< /Type /Catalog /Pages 2 0 R >>",
|
||||
b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
|
||||
b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>",
|
||||
b"<< /Length %d >>\nstream\n%s\nendstream" % (len(stream), stream),
|
||||
b"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>",
|
||||
]
|
||||
body, offsets = b"%PDF-1.4\n", []
|
||||
for n, obj in enumerate(objs, 1):
|
||||
offsets.append(len(body))
|
||||
body += b"%d 0 obj\n%s\nendobj\n" % (n, obj)
|
||||
xref = len(body)
|
||||
body += b"xref\n0 %d\n0000000000 65535 f \n" % (len(objs) + 1)
|
||||
body += b"".join(b"%010d 00000 n \n" % o for o in offsets)
|
||||
body += b"trailer\n<< /Size %d /Root 1 0 R >>\nstartxref\n%d\n%%%%EOF\n" % (len(objs) + 1, xref)
|
||||
open(sys.argv[1], "wb").write(body)
|
||||
PY
|
||||
clang -O1 -I "$root/reference/mupdf/include" "$(dirname "$0")/mupdf_probe.c" \
|
||||
"$out/libmupdf.a" "$out/libmupdf-third.a" -lm -o "$work/probe" || fail "probe does not link"
|
||||
result=$("$work/probe" "$work/hello.pdf") || fail "probe failed: $result"
|
||||
|
||||
grep -qx 'pages=1' <<<"$result" || fail "page count: $result"
|
||||
grep -qx 'bounds=612x792' <<<"$result" || fail "page bounds: $result"
|
||||
grep -qx 'pixmap=1224x1584' <<<"$result" || fail "pixmap size: $result"
|
||||
grep -qE '^dark=[1-9][0-9]*$' <<<"$result" || fail "page renders blank: $result"
|
||||
grep -qx 'text=Ely Office contract' <<<"$result" || fail "text extraction: $result"
|
||||
nm -u "$out/libmupdf-pkcs7.a" 2>/dev/null | grep -q 'PKCS7_' || fail "signing helper is a stub: no OpenSSL PKCS7 symbols"
|
||||
echo "mupdf: render, text and OpenSSL signing helper pass"
|
||||
@@ -0,0 +1,30 @@
|
||||
#include <mupdf/fitz.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
fz_context *ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT);
|
||||
if (argc != 2 || !ctx) return 2;
|
||||
fz_register_document_handlers(ctx);
|
||||
fz_try(ctx) {
|
||||
fz_document *doc = fz_open_document(ctx, argv[1]);
|
||||
fz_page *page = fz_load_page(ctx, doc, 0);
|
||||
fz_rect box = fz_bound_page(ctx, page);
|
||||
fz_pixmap *pix = fz_new_pixmap_from_page(ctx, page, fz_scale(2, 2), fz_device_rgb(ctx), 0);
|
||||
long dark = 0;
|
||||
for (long i = 0; i < (long)pix->w * pix->h; i++)
|
||||
if (pix->samples[i * pix->n] < 128) dark++;
|
||||
printf("pages=%d\nbounds=%.0fx%.0f\npixmap=%dx%d\ndark=%ld\n", fz_count_pages(ctx, doc),
|
||||
box.x1 - box.x0, box.y1 - box.y0, pix->w, pix->h, dark);
|
||||
fz_stext_page *text = fz_new_stext_page_from_page(ctx, page, NULL);
|
||||
fz_buffer *buf = fz_new_buffer(ctx, 256);
|
||||
fz_output *out = fz_new_output_with_buffer(ctx, buf);
|
||||
fz_print_stext_page_as_text(ctx, out, text);
|
||||
fz_close_output(ctx, out);
|
||||
printf("text=%.*s\n", (int)fz_buffer_storage(ctx, buf, NULL), fz_string_from_buffer(ctx, buf));
|
||||
}
|
||||
fz_catch(ctx) {
|
||||
fprintf(stderr, "mupdf: %s\n", fz_caught_message(ctx));
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user