Registry / RepositoryTypeScriptRustPython
Rollpie
ReadmeFiles
Versions
Info
Download
0.4.01085.8 KB2026-10-160.3.21087.6 KB2026-10-160.3.11087.5 KB2026-10-160.3.0117.8 KB2026-10-160.2.1106.1 KB2026-10-150.2.0105.5 KB2026-10-15
Version
0.4.0
Copyright
Rollpie, Irohabook
Publisher
math
Published
2026-10-16
Size
1085.8 KB
Downloads
19
Checksum
df10d3447b809454aba0a9fd252d07428b819520aff22324461ba2dcbf1e43fb
Dependencies
rust/[email protected]
rust/[email protected]

web.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
//! Entry points for calling from JavaScript. Included only when building for wasm32
//!
//! It works like suki's wasm.rs. Text and fonts are passed as bytes written into memory obtained
//! from hongoAllocate. Each function returns 0 on success and 1 on failure, and puts the result,
//! or the error message, where hongoResultPointer and hongoResultLength point.
//!
//! suki's own entry points are linked in too, so every name here starts with hongo.

use crate::font::{self, Library};
use crate::style::Style;
use crate::svg::{self, GlyphPath};
use std::cell::RefCell;
use std::path::Path;


thread_local! {
    /// The loaded typefaces. Empty until hongoLoadFonts is called
    static LIBRARY: RefCell<Option<Library>> = const { RefCell::new(None) };

    /// The path data of the glyphs drawn so far
    static PATH: RefCell<GlyphPath> = RefCell::new(GlyphPath::new());

    /// The result of the last call
    static RESULT: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
}


/// Allocates length bytes of memory for passing data
#[unsafe(export_name = "hongoAllocate")]
pub extern "C" fn allocate(length: usize) -> *mut u8 {
    let mut buffer: Vec<u8> = Vec::with_capacity(length);
    let pointer = buffer.as_mut_ptr();
    std::mem::forget(buffer);

    pointer
}


/// Frees memory obtained from hongoAllocate
///
/// # Safety
///
/// pointer must be what hongoAllocate returned, and length must be the length passed to it
#[unsafe(export_name = "hongoDeallocate")]
pub unsafe extern "C" fn deallocate(pointer: *mut u8, length: usize) {
    drop(unsafe { Vec::from_raw_parts(pointer, 0, length) });
}


/// Where the result of the last call is stored
#[unsafe(export_name = "hongoResultPointer")]
pub extern "C" fn result_pointer() -> *const u8 {
    RESULT.with(|slot| slot.borrow().as_ptr())
}


/// The length of the result of the last call (bytes)
#[unsafe(export_name = "hongoResultLength")]
pub extern "C" fn result_length() -> usize {
    RESULT.with(|slot| slot.borrow().len())
}


/// Loads the math typeface and the typeface for Japanese text. A japanese_length of 0 means
/// there is no typeface for Japanese text
///
/// # Safety
///
/// Each pointer must point to the given number of bytes that can be read
#[unsafe(export_name = "hongoLoadFonts")]
pub unsafe extern "C" fn load_fonts(
    math_pointer: *const u8,
    math_length: usize,
    japanese_pointer: *const u8,
    japanese_length: usize,
) -> u32 {
    let math = unsafe { std::slice::from_raw_parts(math_pointer, math_length) }.to_vec();
    let japanese = if japanese_length == 0 {
        None
    } else {
        Some(unsafe { std::slice::from_raw_parts(japanese_pointer, japanese_length) }.to_vec())
    };

    finish(match font::open_bytes(math, japanese) {
        Ok(loaded) => {
            LIBRARY.with(|slot| *slot.borrow_mut() = Some(loaded));
            PATH.with(|slot| slot.borrow_mut().clear());

            Ok(Vec::new())
        }
        Err(reason) => Err(reason.message),
    })
}


/// Typesets a document and writes each page as SVG
///
/// The result is the characters that no typeface has, then the SVG of each page, separated by
/// NUL
///
/// # Safety
///
/// pointer must point to length bytes that can be read
#[unsafe(export_name = "hongoTypeset")]
pub unsafe extern "C" fn typeset(pointer: *const u8, length: usize) -> u32 {
    let source = match unsafe { read_text(pointer, length) } {
        Ok(source) => source,
        Err(reason) => return finish(Err(reason)),
    };

    finish(with_library(|library| {
        let paged = crate::typeset(&source, library, &Style::default(), Path::new("."))
            .map_err(|reason| reason.message)?;
        let mut out: String = paged.missing.iter().collect();

        PATH.with(|slot| {
            let mut path = slot.borrow_mut();

            for (index, one) in paged.page.iter().enumerate() {
                out.push('\0');
                out.push_str(&svg::render(one, library, &format!("p{index}"), &mut path));
            }
        });

        Ok(out.into_bytes())
    }))
}


/// Typesets a document into a PDF. The result is the PDF itself
///
/// # Safety
///
/// pointer must point to length bytes that can be read
#[unsafe(export_name = "hongoPdf")]
pub unsafe extern "C" fn pdf(pointer: *const u8, length: usize) -> u32 {
    let source = match unsafe { read_text(pointer, length) } {
        Ok(source) => source,
        Err(reason) => return finish(Err(reason)),
    };

    finish(with_library(|library| {
        crate::render(&source, library, &Style::default(), Path::new("."))
            .map(|made| made.data)
            .map_err(|reason| reason.message)
    }))
}


/// Reads the text that the caller wrote
///
/// # Safety
///
/// pointer must point to length bytes that can be read
unsafe fn read_text(pointer: *const u8, length: usize) -> Result<String, String> {
    let bytes = unsafe { std::slice::from_raw_parts(pointer, length) };

    match std::str::from_utf8(bytes) {
        Ok(text) => Ok(text.to_string()),
        Err(_) => Err("the text passed in is not UTF-8".to_string()),
    }
}


/// Stores the result, then returns 0 on success and 1 on failure
fn finish(outcome: Result<Vec<u8>, String>) -> u32 {
    let (status, data) = match outcome {
        Ok(data) => (0, data),
        Err(text) => (1, text.into_bytes()),
    };

    RESULT.with(|slot| *slot.borrow_mut() = data);

    status
}


/// Works with the loaded typefaces. Fails if none have been loaded yet
fn with_library(
    work: impl FnOnce(&Library) -> Result<Vec<u8>, String>,
) -> Result<Vec<u8>, String> {
    LIBRARY.with(|slot| match slot.borrow().as_ref() {
        Some(loaded) => work(loaded),
        None => Err("called before the typefaces were loaded; call hongoLoadFonts first".to_string()),
    })
}