use xberg_native_pdf::layout::TextSpan;
pub(crate) fn is_unrotated(span: &TextSpan) -> bool {
span.rotation_degrees.abs() <= f32::EPSILON
}
pub(crate) fn has_same_rotation(first: &TextSpan, second: &TextSpan) -> bool {
(first.rotation_degrees - second.rotation_degrees).abs() <= f32::EPSILON
}
pub(crate) fn is_ltr_writing_mode(span: &TextSpan) -> bool {
span.wmode == 0 && !span.rtl_draw_logical
}
pub(crate) fn is_horizontal_ltr(span: &TextSpan) -> bool {
is_ltr_writing_mode(span) && is_unrotated(span)
}
pub(crate) fn upright_origin(span: &TextSpan) -> (f32, f32) {
if is_unrotated(span) {
return (span.bbox.x, span.bbox.y);
}
let (sin, cos) = (-span.rotation_degrees).to_radians().sin_cos();
(
span.bbox.x * cos - span.bbox.y * sin,
span.bbox.x * sin + span.bbox.y * cos,
)
}
pub(crate) fn upright_advance_extent(span: &TextSpan) -> (f32, f32) {
let (start, _) = upright_origin(span);
(start, start + span.bbox.width)
}
pub(crate) fn upright_cross_extent(span: &TextSpan) -> (f32, f32) {
let (_, low) = upright_origin(span);
(low, low + span.bbox.height)
}
#[cfg(test)]
mod tests {
use super::*;
use xberg_native_pdf::geometry::Rect;
fn rotated_span(x: f32, y: f32, width: f32, height: f32, rotation_degrees: f32) -> TextSpan {
TextSpan {
text: "x".to_string(),
bbox: Rect { x, y, width, height },
font_size: height,
rotation_degrees,
..TextSpan::default()
}
}
#[test]
fn should_return_raw_bbox_when_span_is_unrotated() {
let span = rotated_span(100.0, 700.0, 40.0, 10.0, 0.0);
assert_eq!(upright_origin(&span), (100.0, 700.0));
assert_eq!(upright_advance_extent(&span), (100.0, 140.0));
assert_eq!(upright_cross_extent(&span), (700.0, 710.0));
}
#[test]
fn should_swap_axes_when_span_is_rotated_ninety_degrees() {
let span = rotated_span(100.0, 700.0, 40.0, 10.0, 90.0);
let (advance, cross) = upright_origin(&span);
assert!((advance - 700.0).abs() < 1e-3, "advance axis was {advance}");
assert!((cross - -100.0).abs() < 1e-3, "cross axis was {cross}");
let (start, end) = upright_advance_extent(&span);
assert!((start - 700.0).abs() < 1e-3 && (end - 740.0).abs() < 1e-3);
}
#[test]
fn should_treat_rotated_span_as_ltr_writing_mode_but_not_horizontal_ltr() {
let span = rotated_span(100.0, 700.0, 40.0, 10.0, 90.0);
assert!(is_ltr_writing_mode(&span), "rotated text is still horizontal LTR text");
assert!(
!is_horizontal_ltr(&span),
"rotated text is not painted on the page axis"
);
assert!(!is_unrotated(&span));
}
#[test]
fn should_reject_vertical_and_rtl_writing_modes_regardless_of_rotation() {
let mut vertical = rotated_span(100.0, 700.0, 40.0, 10.0, 90.0);
vertical.wmode = 1;
let mut right_to_left = rotated_span(100.0, 700.0, 40.0, 10.0, 0.0);
right_to_left.rtl_draw_logical = true;
assert!(!is_ltr_writing_mode(&vertical));
assert!(!is_ltr_writing_mode(&right_to_left));
assert!(!is_horizontal_ltr(&right_to_left));
}
#[test]
fn should_match_rotation_only_between_equally_rotated_spans() {
let upright = rotated_span(0.0, 0.0, 10.0, 10.0, 0.0);
let rotated = rotated_span(0.0, 0.0, 10.0, 10.0, 90.0);
let also_rotated = rotated_span(50.0, 0.0, 10.0, 10.0, 90.0);
assert!(has_same_rotation(&rotated, &also_rotated));
assert!(!has_same_rotation(&upright, &rotated));
assert!(has_same_rotation(&upright, &upright.clone()));
}
}