Compare commits

..

11 commits

Author SHA1 Message Date
040afcc435
extracts pdf items using mupdf-sys directly
Some checks failed
/ test (push) Failing after 24s
2026-01-06 08:00:38 -08:00
a677cd8a33
wip 2026-01-05 18:33:44 -08:00
103f986bc0
wip 2026-01-05 17:53:57 -08:00
f9a24f4c48
switching to using mupdf-sys directly since mupdf hides all the necessary functionality 2026-01-05 14:17:49 -08:00
fcf1c63cb7
wip 2026-01-05 11:27:52 -08:00
c58bc23904
wip 2026-01-05 09:41:56 -08:00
3d66c853f6
wip 2026-01-05 09:30:06 -08:00
e9830566c0
fix typo in parse_powerisa_pdf.py 2026-01-05 06:57:29 -08:00
442afe5f06
wip 2026-01-04 20:01:13 -08:00
45e8925d34
use Display for errors rather than Debug 2026-01-04 18:33:41 -08:00
b1d83b1d84
switch font names to not include tag 2026-01-04 18:33:07 -08:00
5 changed files with 2672 additions and 423 deletions

97
Cargo.lock generated
View file

@ -154,19 +154,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "mupdf"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a6499267155b9ae03ff8e53c456d0bfff988b2647d62ff1df038f39ebe93a0c"
dependencies = [
"bitflags",
"mupdf-sys",
"num_enum",
"once_cell",
"zerocopy",
]
[[package]]
name = "mupdf-sys"
version = "0.5.0"
@ -190,41 +177,13 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "num_enum"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c"
dependencies = [
"num_enum_derive",
"rustversion",
]
[[package]]
name = "num_enum_derive"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "parse_powerisa_pdf"
version = "0.1.0"
dependencies = [
"indexmap",
"libm",
"mupdf",
"mupdf-sys",
"quick-xml",
"serde",
]
@ -235,15 +194,6 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "proc-macro-crate"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
dependencies = [
"toml_edit",
]
[[package]]
name = "proc-macro2"
version = "1.0.104"
@ -307,12 +257,6 @@ version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "serde"
version = "1.0.228"
@ -360,36 +304,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "toml_datetime"
version = "0.7.5+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
dependencies = [
"serde_core",
]
[[package]]
name = "toml_edit"
version = "0.23.10+spec-1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
dependencies = [
"indexmap",
"toml_datetime",
"toml_parser",
"winnow",
]
[[package]]
name = "toml_parser"
version = "1.0.6+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
dependencies = [
"winnow",
]
[[package]]
name = "unicode-ident"
version = "1.0.22"
@ -402,15 +316,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "winnow"
version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
dependencies = [
"memchr",
]
[[package]]
name = "zerocopy"
version = "0.8.31"

View file

@ -13,6 +13,6 @@ rust-version = "1.89.0"
[dependencies]
indexmap = "2.12.1"
libm = "0.2.15"
mupdf = { version = "0.5.0", default-features = false }
mupdf-sys = { version = "0.5.0", default-features = false }
quick-xml = { version = "0.38.4", features = ["serialize"] }
serde = { version = "1.0.228", features = ["derive"] }

View file

@ -765,7 +765,7 @@ class Page:
unprocessed_non_text: SetById[LTLine | LTRect]
@staticmethod
def from_lt_page(page_num: int, page: LTPage) -> Page:
def from_lt_page(page_num: int, page: LTPage, first_seen_fonts: defaultdict[str, set[float]]) -> Page:
qt: defaultdict[TextSection, QuadTree[Char | LTLine | LTRect]] = defaultdict(QuadTree)
unprocessed_chars = defaultdict(lambda: defaultdict(SetById[Char]))
unprocessed_non_text: SetById[LTLine | LTRect] = SetById()
@ -804,20 +804,25 @@ class Page:
raise AssertionError(
f"char not in text section: {element}\npage_num={page_num}")
continue
font_size = round(element.size, 3)
char = Char(
text=element.get_text(),
font=Font(font_name=element.fontname, size=round(element.size, 3)),
font=Font(font_name=element.fontname, size=font_size),
adv=element.adv,
min_x=element.x0,
min_y=element.y0,
max_x=element.x1,
max_y=element.y1,
)
if font_size not in first_seen_fonts[element.fontname]:
first_seen_fonts[element.fontname].add(font_size)
print(f"first seen font: {element.fontname!r} {font_size}: page {page_num} {char!r}")
qt[text_section].insert(char.min_x, char.min_y, char)
unprocessed_chars[text_section][char.font].add(char)
for i in unprocessed_chars.values():
for j in i.values():
j.sort(key=Char.top_down_left_to_right_sort_key)
for text_section, i in unprocessed_chars.items():
for chars in i.values():
chars.sort(key=Char.top_down_left_to_right_sort_key)
print(f"first char: {text_section!r}: {next(iter(chars), None)!r}")
unknown_fonts=[]
unknown_font_errors=[]
for i in unprocessed_chars.values():
@ -1181,13 +1186,14 @@ class Parser:
def __pages_gen(file: Path, page_numbers: Iterable[int] | None) -> Generator[Page, None, None]:
if page_numbers is not None:
page_numbers = sorted(i - 1 for i in page_numbers)
first_seen_fonts = defaultdict(set)
for i, page in enumerate(extract_pages(file, page_numbers=page_numbers)):
if page_numbers is not None:
page_num = page_numbers[i] + 1
else:
page_num = i + 1
print(f"page {page_num}")
yield Page.from_lt_page(page_num=page_num, page=page)
yield Page.from_lt_page(page_num=page_num, page=page, first_seen_fonts=first_seen_fonts)
def parse_pdf(self, file: Path, page_numbers: Iterable[int] | None = None):
self.pages = Pages(pages_gen=Parser.__pages_gen(
@ -1503,7 +1509,7 @@ class Parser:
f"instruction bit fields box has wrong number of horizontal lines:\n{h_lines}")
if len(v_lines) < 2:
raise InsnParseError(
f"instruction bit fields box has too few vertical lines:\n{h_lines}")
f"instruction bit fields box has too few vertical lines:\n{v_lines}")
bottom_line, top_line = h_lines
box_min_x = v_lines[0].x0
box_max_x = v_lines[-1].x0

File diff suppressed because it is too large Load diff

804
src/mupdf_ffi.rs Normal file
View file

@ -0,0 +1,804 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use mupdf_sys::{
fz_clone_context, fz_color_params, fz_colorspace, fz_concat, fz_context, fz_device,
fz_document, fz_drop_context, fz_drop_device, fz_drop_document, fz_drop_page, fz_drop_path,
fz_drop_text, fz_error_type_FZ_ERROR_GENERIC, fz_font, fz_font_ascender, fz_font_descender,
fz_font_is_bold, fz_font_is_italic, fz_font_name, fz_matrix, fz_matrix_expansion, fz_page,
fz_path, fz_path_walker, fz_point, fz_rect, fz_stroke_state, fz_text, fz_text_item,
fz_text_span, fz_transform_point, fz_transform_point_xy, fz_transform_vector, fz_walk_path,
mupdf_document_page_count, mupdf_drop_error, mupdf_error_t, mupdf_load_page,
mupdf_new_base_context, mupdf_new_derived_device, mupdf_open_document, mupdf_run_page,
};
use std::{
cell::{Cell, UnsafeCell},
ffi::{CStr, c_int, c_void},
fmt,
marker::PhantomData,
mem::ManuallyDrop,
ptr::{self, NonNull},
sync::{Mutex, OnceLock},
};
#[derive(Debug)]
pub(crate) struct MuPdfError {
type_: c_int,
message: String,
}
impl MuPdfError {
fn new_generic(message: impl ToString) -> Self {
Self {
type_: fz_error_type_FZ_ERROR_GENERIC as _,
message: message.to_string(),
}
}
}
impl fmt::Display for MuPdfError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"MuPDF error: type: {}, message: {}",
self.type_, self.message
)
}
}
impl std::error::Error for MuPdfError {}
struct OwnedMuPdfError(NonNull<mupdf_error_t>);
impl Drop for OwnedMuPdfError {
fn drop(&mut self) {
unsafe {
mupdf_drop_error(self.0.as_ptr());
}
}
}
unsafe fn mupdf_try<R>(f: impl FnOnce(&mut *mut mupdf_error_t) -> R) -> Result<R, MuPdfError> {
let mut err = ptr::null_mut();
let retval = f(&mut err);
let Some(err) = NonNull::new(err).map(OwnedMuPdfError) else {
return Ok(retval);
};
unsafe {
Err(MuPdfError {
type_: (*err.0.as_ptr()).type_,
message: CStr::from_ptr((*err.0.as_ptr()).message)
.to_string_lossy()
.into_owned(),
})
}
}
pub(crate) struct Context(NonNull<fz_context>);
impl Context {
fn new() -> Self {
struct BaseContext(NonNull<fz_context>);
unsafe impl Send for BaseContext {}
static CTX: OnceLock<Mutex<BaseContext>> = OnceLock::new();
let base = CTX
.get_or_init(|| {
let ctx = unsafe { mupdf_new_base_context() };
let Some(ctx) = NonNull::new(ctx).map(BaseContext) else {
panic!("failed to allocate a MuPDF context");
};
Mutex::new(ctx)
})
.lock()
.expect("not poisoned");
let ctx = unsafe { fz_clone_context(base.0.as_ptr()) };
let Some(ctx) = NonNull::new(ctx).map(Self) else {
drop(base);
panic!("failed to clone a MuPDF context");
};
ctx
}
pub(crate) fn with<R>(f: impl FnOnce(&Self) -> R) -> R {
thread_local! {
static CTX: Context = Context::new();
}
CTX.with(f)
}
pub(crate) fn as_ref(&self) -> ContextRef<'_> {
unsafe { ContextRef::from_ptr(self.0.as_ptr()) }
}
}
impl Drop for Context {
fn drop(&mut self) {
unsafe {
fz_drop_context(self.0.as_ptr());
}
}
}
#[derive(Clone, Copy)]
pub(crate) struct ContextRef<'ctx>(&'ctx UnsafeCell<fz_context>);
impl<'ctx> ContextRef<'ctx> {
unsafe fn from_ptr(ptr: *mut fz_context) -> Self {
Self(unsafe { &*ptr.cast() })
}
}
impl<'ctx> From<&'ctx Context> for ContextRef<'ctx> {
fn from(value: &'ctx Context) -> Self {
value.as_ref()
}
}
pub(crate) struct Document<'ctx> {
ptr: *mut fz_document,
ctx: ContextRef<'ctx>,
}
impl<'ctx> Document<'ctx> {
pub(crate) fn open(
ctx: impl Into<ContextRef<'ctx>>,
file_name: &CStr,
) -> Result<Document<'ctx>, MuPdfError> {
let ctx = ctx.into();
unsafe {
mupdf_try(|errptr| mupdf_open_document(ctx.0.get(), file_name.as_ptr(), errptr))
.map(|ptr| Document { ptr, ctx })
}
}
pub(crate) fn page_count(&self) -> Result<usize, MuPdfError> {
unsafe {
mupdf_try(|errptr| mupdf_document_page_count(self.ctx.0.get(), self.ptr, errptr))?
.try_into()
.map_err(MuPdfError::new_generic)
}
}
pub(crate) fn load_page(&self, page: usize) -> Result<Page<'ctx>, MuPdfError> {
let page = page.try_into().map_err(MuPdfError::new_generic)?;
unsafe {
mupdf_try(|errptr| mupdf_load_page(self.ctx.0.get(), self.ptr, page, errptr))
.map(|ptr| Page { ptr, ctx: self.ctx })
}
}
}
impl<'ctx> Drop for Document<'ctx> {
fn drop(&mut self) {
unsafe {
fz_drop_document(self.ctx.0.get(), self.ptr);
}
}
}
pub(crate) struct Page<'ctx> {
ptr: *mut fz_page,
ctx: ContextRef<'ctx>,
}
impl<'ctx> Page<'ctx> {
pub(crate) fn ctx(&self) -> ContextRef<'ctx> {
self.ctx
}
pub(crate) fn run<T>(
&self,
device: &Device<'ctx, T>,
ctm: fz_matrix,
) -> Result<(), MuPdfError> {
unsafe {
mupdf_try(|errptr| {
mupdf_run_page(
self.ctx.0.get(),
self.ptr,
device.dev,
ctm,
ptr::null_mut(),
errptr,
)
})
}
}
}
impl<'ctx> Drop for Page<'ctx> {
fn drop(&mut self) {
unsafe {
fz_drop_page(self.ctx.0.get(), self.ptr);
}
}
}
pub(crate) struct Device<'ctx, T: 'ctx> {
dev: *mut fz_device,
ctx: ContextRef<'ctx>,
_phantom: PhantomData<Box<Cell<T>>>,
}
pub(crate) trait DeviceCallbacks<'ctx> {
fn fill_path(&self, ctx: ContextRef<'ctx>, path: &Path<'ctx>, even_odd: bool, ctm: fz_matrix);
fn stroke_path(&self, ctx: ContextRef<'ctx>, path: &Path<'ctx>, ctm: fz_matrix);
fn clip_path(
&self,
ctx: ContextRef<'ctx>,
path: &Path<'ctx>,
even_odd: bool,
ctm: fz_matrix,
scissor: fz_rect,
);
fn clip_stroke_path(
&self,
ctx: ContextRef<'ctx>,
path: &Path<'ctx>,
ctm: fz_matrix,
scissor: fz_rect,
);
fn fill_text(&self, ctx: ContextRef<'ctx>, text: &Text<'ctx>, ctm: fz_matrix);
fn stroke_text(&self, ctx: ContextRef<'ctx>, text: &Text<'ctx>, ctm: fz_matrix);
fn clip_text(&self, ctx: ContextRef<'ctx>, text: &Text<'ctx>, ctm: fz_matrix, scissor: fz_rect);
fn clip_stroke_text(
&self,
ctx: ContextRef<'ctx>,
text: &Text<'ctx>,
ctm: fz_matrix,
scissor: fz_rect,
);
fn ignore_text(&self, ctx: ContextRef<'ctx>, text: &Text<'ctx>, ctm: fz_matrix);
}
impl<'ctx, T: DeviceCallbacks<'ctx>> Device<'ctx, T> {
pub(crate) fn new(ctx: impl Into<ContextRef<'ctx>>, value: Box<T>) -> Result<Self, MuPdfError> {
let ctx = ctx.into();
unsafe {
let dev_ptr = mupdf_try(|errptr| {
mupdf_new_derived_device::<DeviceStruct<T>>(
ctx.0.get(),
c"parse_powerisa_pdf::mupdf_ffi::Device",
errptr,
)
})?;
let retval = Device {
dev: dev_ptr.cast(),
ctx,
_phantom: PhantomData,
};
(&raw mut (*dev_ptr).value).write(value);
let fz_device {
drop_device,
fill_path,
stroke_path,
clip_path,
clip_stroke_path,
fill_text,
stroke_text,
clip_text,
clip_stroke_text,
ignore_text,
..
} = &mut (*dev_ptr).base;
*drop_device = Some(Self::drop_device_fn);
*fill_path = Some(Self::fill_path_fn);
*stroke_path = Some(Self::stroke_path_fn);
*clip_path = Some(Self::clip_path_fn);
*clip_stroke_path = Some(Self::clip_stroke_path_fn);
*fill_text = Some(Self::fill_text_fn);
*stroke_text = Some(Self::stroke_text_fn);
*clip_text = Some(Self::clip_text_fn);
*clip_stroke_text = Some(Self::clip_stroke_text_fn);
*ignore_text = Some(Self::ignore_text_fn);
Ok(retval)
}
}
pub(crate) fn get(&self) -> &T {
unsafe { &(*self.dev.cast::<DeviceStruct<T>>()).value }
}
unsafe extern "C" fn drop_device_fn(_ctx: *mut fz_context, dev: *mut fz_device) {
unsafe {
(&raw mut (*dev.cast::<DeviceStruct<T>>()).value).drop_in_place();
}
}
unsafe extern "C" fn fill_path_fn(
ctx: *mut fz_context,
dev: *mut fz_device,
path: *const fz_path,
even_odd: c_int,
ctm: fz_matrix,
_color_space: *mut fz_colorspace,
_color: *const f32,
_alpha: f32,
_color_params: fz_color_params,
) {
let ctx = unsafe { ContextRef::from_ptr(ctx) };
let this = unsafe { &mut (*dev.cast::<DeviceStruct<T>>()).value };
this.fill_path(
ctx,
&ManuallyDrop::new(Path {
ptr: path.cast_mut(),
ctx,
}),
even_odd != 0,
ctm,
);
}
unsafe extern "C" fn stroke_path_fn(
ctx: *mut fz_context,
dev: *mut fz_device,
path: *const fz_path,
_stroke_state: *const fz_stroke_state,
ctm: fz_matrix,
_color_space: *mut fz_colorspace,
_color: *const f32,
_alpha: f32,
_color_params: fz_color_params,
) {
let ctx = unsafe { ContextRef::from_ptr(ctx) };
let this = unsafe { &mut (*dev.cast::<DeviceStruct<T>>()).value };
this.stroke_path(
ctx,
&ManuallyDrop::new(Path {
ptr: path.cast_mut(),
ctx,
}),
ctm,
);
}
unsafe extern "C" fn clip_path_fn(
ctx: *mut fz_context,
dev: *mut fz_device,
path: *const fz_path,
even_odd: ::std::os::raw::c_int,
ctm: fz_matrix,
scissor: fz_rect,
) {
let ctx = unsafe { ContextRef::from_ptr(ctx) };
let this = unsafe { &mut (*dev.cast::<DeviceStruct<T>>()).value };
this.clip_path(
ctx,
&ManuallyDrop::new(Path {
ptr: path.cast_mut(),
ctx,
}),
even_odd != 0,
ctm,
scissor,
);
}
unsafe extern "C" fn clip_stroke_path_fn(
ctx: *mut fz_context,
dev: *mut fz_device,
path: *const fz_path,
_stroke_state: *const fz_stroke_state,
ctm: fz_matrix,
scissor: fz_rect,
) {
let ctx = unsafe { ContextRef::from_ptr(ctx) };
let this = unsafe { &mut (*dev.cast::<DeviceStruct<T>>()).value };
this.clip_stroke_path(
ctx,
&ManuallyDrop::new(Path {
ptr: path.cast_mut(),
ctx,
}),
ctm,
scissor,
);
}
unsafe extern "C" fn fill_text_fn(
ctx: *mut fz_context,
dev: *mut fz_device,
text: *const fz_text,
ctm: fz_matrix,
_color_space: *mut fz_colorspace,
_color: *const f32,
_alpha: f32,
_color_params: fz_color_params,
) {
let ctx = unsafe { ContextRef::from_ptr(ctx) };
let this = unsafe { &mut (*dev.cast::<DeviceStruct<T>>()).value };
this.fill_text(
ctx,
&ManuallyDrop::new(Text {
ptr: text.cast_mut(),
ctx,
}),
ctm,
);
}
unsafe extern "C" fn stroke_text_fn(
ctx: *mut fz_context,
dev: *mut fz_device,
text: *const fz_text,
_stroke_state: *const fz_stroke_state,
ctm: fz_matrix,
_color_space: *mut fz_colorspace,
_color: *const f32,
_alpha: f32,
_color_params: fz_color_params,
) {
let ctx = unsafe { ContextRef::from_ptr(ctx) };
let this = unsafe { &mut (*dev.cast::<DeviceStruct<T>>()).value };
this.stroke_text(
ctx,
&ManuallyDrop::new(Text {
ptr: text.cast_mut(),
ctx,
}),
ctm,
);
}
unsafe extern "C" fn clip_text_fn(
ctx: *mut fz_context,
dev: *mut fz_device,
text: *const fz_text,
ctm: fz_matrix,
scissor: fz_rect,
) {
let ctx = unsafe { ContextRef::from_ptr(ctx) };
let this = unsafe { &mut (*dev.cast::<DeviceStruct<T>>()).value };
this.clip_text(
ctx,
&ManuallyDrop::new(Text {
ptr: text.cast_mut(),
ctx,
}),
ctm,
scissor,
);
}
unsafe extern "C" fn clip_stroke_text_fn(
ctx: *mut fz_context,
dev: *mut fz_device,
text: *const fz_text,
_stroke_state: *const fz_stroke_state,
ctm: fz_matrix,
scissor: fz_rect,
) {
let ctx = unsafe { ContextRef::from_ptr(ctx) };
let this = unsafe { &mut (*dev.cast::<DeviceStruct<T>>()).value };
this.clip_stroke_text(
ctx,
&ManuallyDrop::new(Text {
ptr: text.cast_mut(),
ctx,
}),
ctm,
scissor,
);
}
unsafe extern "C" fn ignore_text_fn(
ctx: *mut fz_context,
dev: *mut fz_device,
text: *const fz_text,
ctm: fz_matrix,
) {
let ctx = unsafe { ContextRef::from_ptr(ctx) };
let this = unsafe { &mut (*dev.cast::<DeviceStruct<T>>()).value };
this.ignore_text(
ctx,
&ManuallyDrop::new(Text {
ptr: text.cast_mut(),
ctx,
}),
ctm,
);
}
}
impl<'ctx, T> Drop for Device<'ctx, T> {
fn drop(&mut self) {
unsafe {
// FIXME: fz_close_device may throw exceptions
// fz_close_device(self.ctx.0.get(), self.dev);
fz_drop_device(self.ctx.0.get(), self.dev);
}
}
}
#[repr(C)]
struct DeviceStruct<T> {
base: fz_device,
value: Box<T>,
}
pub(crate) trait PathWalker<'ctx> {
fn move_to(&mut self, ctx: ContextRef<'ctx>, x: f32, y: f32);
fn line_to(&mut self, ctx: ContextRef<'ctx>, x: f32, y: f32);
fn curve_to(
&mut self,
ctx: ContextRef<'ctx>,
x1: f32,
y1: f32,
x2: f32,
y2: f32,
x3: f32,
y3: f32,
);
fn close_path(&mut self, ctx: ContextRef<'ctx>);
fn rect_to(&mut self, ctx: ContextRef<'ctx>, x1: f32, y1: f32, x2: f32, y2: f32) {
self.move_to(ctx, x1, y1);
self.move_to(ctx, x2, y1);
self.move_to(ctx, x2, y2);
self.move_to(ctx, x1, y2);
self.close_path(ctx);
}
}
impl<'ctx, T: ?Sized + PathWalker<'ctx>> PathWalker<'ctx> for &'_ mut T {
fn move_to(&mut self, ctx: ContextRef<'ctx>, x: f32, y: f32) {
T::move_to(self, ctx, x, y);
}
fn line_to(&mut self, ctx: ContextRef<'ctx>, x: f32, y: f32) {
T::line_to(self, ctx, x, y);
}
fn curve_to(
&mut self,
ctx: ContextRef<'ctx>,
x1: f32,
y1: f32,
x2: f32,
y2: f32,
x3: f32,
y3: f32,
) {
T::curve_to(self, ctx, x1, y1, x2, y2, x3, y3);
}
fn close_path(&mut self, ctx: ContextRef<'ctx>) {
T::close_path(self, ctx);
}
fn rect_to(&mut self, ctx: ContextRef<'ctx>, x1: f32, y1: f32, x2: f32, y2: f32) {
T::rect_to(self, ctx, x1, y1, x2, y2);
}
}
pub(crate) struct Path<'ctx> {
ptr: *mut fz_path,
ctx: ContextRef<'ctx>,
}
impl<'ctx> Path<'ctx> {
pub(crate) fn walk<W: PathWalker<'ctx>>(&self, mut walker: W) {
unsafe {
fz_walk_path(
self.ctx.0.get(),
self.ptr,
const {
&fz_path_walker {
moveto: Some(Self::move_to_fn::<W>),
lineto: Some(Self::line_to_fn::<W>),
curveto: Some(Self::curve_to_fn::<W>),
closepath: Some(Self::close_path_fn::<W>),
quadto: None,
curvetov: None,
curvetoy: None,
rectto: Some(Self::rect_to_fn::<W>),
}
},
(&raw mut walker).cast(),
);
}
}
unsafe extern "C" fn move_to_fn<W: PathWalker<'ctx>>(
ctx: *mut fz_context,
arg: *mut c_void,
x: f32,
y: f32,
) {
let ctx = unsafe { ContextRef::from_ptr(ctx) };
let this = unsafe { &mut *arg.cast::<W>() };
this.move_to(ctx, x, y);
}
unsafe extern "C" fn line_to_fn<W: PathWalker<'ctx>>(
ctx: *mut fz_context,
arg: *mut c_void,
x: f32,
y: f32,
) {
let ctx = unsafe { ContextRef::from_ptr(ctx) };
let this = unsafe { &mut *arg.cast::<W>() };
this.line_to(ctx, x, y);
}
unsafe extern "C" fn curve_to_fn<W: PathWalker<'ctx>>(
ctx: *mut fz_context,
arg: *mut c_void,
x1: f32,
y1: f32,
x2: f32,
y2: f32,
x3: f32,
y3: f32,
) {
let ctx = unsafe { ContextRef::from_ptr(ctx) };
let this = unsafe { &mut *arg.cast::<W>() };
this.curve_to(ctx, x1, y1, x2, y2, x3, y3);
}
unsafe extern "C" fn close_path_fn<W: PathWalker<'ctx>>(
ctx: *mut fz_context,
arg: *mut c_void,
) {
let ctx = unsafe { ContextRef::from_ptr(ctx) };
let this = unsafe { &mut *arg.cast::<W>() };
this.close_path(ctx);
}
unsafe extern "C" fn rect_to_fn<W: PathWalker<'ctx>>(
ctx: *mut fz_context,
arg: *mut c_void,
x1: f32,
y1: f32,
x2: f32,
y2: f32,
) {
let ctx = unsafe { ContextRef::from_ptr(ctx) };
let this = unsafe { &mut *arg.cast::<W>() };
this.rect_to(ctx, x1, y1, x2, y2);
}
}
impl<'ctx> Drop for Path<'ctx> {
fn drop(&mut self) {
unsafe {
fz_drop_path(self.ctx.0.get(), self.ptr);
}
}
}
pub(crate) struct Text<'ctx> {
ptr: *mut fz_text,
ctx: ContextRef<'ctx>,
}
impl<'ctx> Drop for Text<'ctx> {
fn drop(&mut self) {
unsafe {
fz_drop_text(self.ctx.0.get(), self.ptr);
}
}
}
impl<'ctx> Text<'ctx> {
pub(crate) fn spans<'a>(&'a self) -> TextSpanIter<'a, 'ctx> {
TextSpanIter {
ptr: unsafe { NonNull::new((*self.ptr).head).map(|ptr| &*ptr.as_ptr().cast()) },
ctx: self.ctx,
_phantom: PhantomData,
}
}
}
#[derive(Clone)]
pub(crate) struct TextSpanIter<'a, 'ctx> {
ptr: Option<&'a UnsafeCell<fz_text_span>>,
ctx: ContextRef<'ctx>,
_phantom: PhantomData<&'a Text<'ctx>>,
}
impl<'a, 'ctx> Iterator for TextSpanIter<'a, 'ctx> {
type Item = TextSpanRef<'a, 'ctx>;
fn next(&mut self) -> Option<Self::Item> {
let ptr = self.ptr?;
self.ptr = unsafe { NonNull::new((*ptr.get()).next).map(|ptr| &*ptr.as_ptr().cast()) };
Some(TextSpanRef {
ptr,
ctx: self.ctx,
_phantom: PhantomData,
})
}
}
#[derive(Copy, Clone)]
pub(crate) struct TextSpanRef<'a, 'ctx> {
ptr: &'a UnsafeCell<fz_text_span>,
ctx: ContextRef<'ctx>,
_phantom: PhantomData<&'a Text<'ctx>>,
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub(crate) enum WriteMode {
Horizontal,
Vertical,
}
impl<'a, 'ctx> TextSpanRef<'a, 'ctx> {
pub(crate) fn get(self) -> &'a UnsafeCell<fz_text_span> {
self.ptr
}
pub(crate) fn font(self) -> FontRef<'a, 'ctx> {
FontRef {
ptr: unsafe { &*(*self.ptr.get()).font.cast::<UnsafeCell<fz_font>>() },
ctx: self.ctx,
_phantom: PhantomData,
}
}
pub(crate) fn trm(self) -> fz_matrix {
unsafe { (*self.ptr.get()).trm }
}
pub(crate) fn write_mode(self) -> WriteMode {
if unsafe { (*self.ptr.get()).wmode() != 0 } {
WriteMode::Vertical
} else {
WriteMode::Horizontal
}
}
pub(crate) fn items(self) -> &'a [fz_text_item] {
let len = unsafe { (*self.ptr.get()).len } as usize;
if len == 0 {
return &[];
}
unsafe { std::slice::from_raw_parts((*self.ptr.get()).items, len) }
}
}
#[derive(Clone, Copy)]
pub(crate) struct FontRef<'a, 'ctx> {
ptr: &'a UnsafeCell<fz_font>,
ctx: ContextRef<'ctx>,
_phantom: PhantomData<&'a Text<'ctx>>,
}
impl<'a, 'ctx> FontRef<'a, 'ctx> {
pub(crate) fn get(self) -> &'a UnsafeCell<fz_font> {
self.ptr
}
pub(crate) fn name(self) -> &'a str {
unsafe { CStr::from_ptr(fz_font_name(self.ctx.0.get(), self.ptr.get())) }
.to_str()
.expect("font name isn't valid UTF-8")
}
pub(crate) fn is_bold(self) -> bool {
unsafe { fz_font_is_bold(self.ctx.0.get(), self.ptr.get()) != 0 }
}
pub(crate) fn is_italic(self) -> bool {
unsafe { fz_font_is_italic(self.ctx.0.get(), self.ptr.get()) != 0 }
}
pub(crate) fn ascender(self) -> f32 {
unsafe { fz_font_ascender(self.ctx.0.get(), self.ptr.get()) }
}
pub(crate) fn descender(self) -> f32 {
unsafe { fz_font_descender(self.ctx.0.get(), self.ptr.get()) }
}
}
pub(crate) fn transform_point(point: fz_point, m: fz_matrix) -> fz_point {
unsafe { fz_transform_point(point, m) }
}
pub(crate) fn transform_point_xy(x: f32, y: f32, m: fz_matrix) -> fz_point {
unsafe { fz_transform_point_xy(x, y, m) }
}
pub(crate) fn transform_vector(vector: fz_point, m: fz_matrix) -> fz_point {
unsafe { fz_transform_vector(vector, m) }
}
pub(crate) fn matrix_expansion(m: fz_matrix) -> f32 {
unsafe { fz_matrix_expansion(m) }
}
pub(crate) fn concat(left: fz_matrix, right: fz_matrix) -> fz_matrix {
unsafe { fz_concat(left, right) }
}
pub(crate) fn add_points(a: fz_point, b: fz_point) -> fz_point {
fz_point {
x: a.x + b.x,
y: a.y + b.y,
}
}
pub(crate) fn point_min_components(a: fz_point, b: fz_point) -> fz_point {
fz_point {
x: a.x.min(b.x),
y: a.y.min(b.y),
}
}
pub(crate) fn point_max_components(a: fz_point, b: fz_point) -> fz_point {
fz_point {
x: a.x.max(b.x),
y: a.y.max(b.y),
}
}