use super::RawTextCoordinate;
use crate::pdf::PageSpec;
use crate::{Error, Result};
pub const EMPIRICAL_COORDINATE_POINTS_PER_UNIT: f64 = 72.0 / 742.0;
pub const EMPIRICAL_PLACEMENT_TOLERANCE_POINTS: f64 = 0.00005;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct EmpiricalPageGeometry {
pub origin_points: [f64; 2],
pub size: PageSpec,
}
impl EmpiricalPageGeometry {
pub fn media_box(self) -> Result<[f64; 4]> {
let [left, bottom] = self.origin_points;
let PageSpec {
width_points,
height_points,
} = self.size;
if !left.is_finite() || !bottom.is_finite() {
return Err(Error::invalid("empirical PDF origin must be finite"));
}
if !width_points.is_finite()
|| !height_points.is_finite()
|| width_points <= 0.0
|| height_points <= 0.0
{
return Err(Error::invalid(
"empirical page dimensions must be finite and positive",
));
}
let right = left + width_points;
let top = bottom + height_points;
if !right.is_finite() || !top.is_finite() || right <= left || top <= bottom {
return Err(Error::invalid(
"empirical page rectangle overflows or collapses at the supplied origin",
));
}
if ((right - left) - width_points).abs() > EMPIRICAL_PLACEMENT_TOLERANCE_POINTS
|| ((top - bottom) - height_points).abs() > EMPIRICAL_PLACEMENT_TOLERANCE_POINTS
{
return Err(Error::invalid(
"empirical PDF origin cannot preserve the page dimension precision",
));
}
Ok([left, bottom, right, top])
}
}
pub fn empirical_page_from_pixels(
pixel_width: u32,
pixel_height: u32,
origin_points: [f64; 2],
) -> Result<EmpiricalPageGeometry> {
let size = pixel_size(pixel_width, pixel_height)?;
let page = EmpiricalPageGeometry {
origin_points,
size,
};
page.media_box()?;
Ok(page)
}
pub fn empirical_image_transform(
page: EmpiricalPageGeometry,
pixel_width: u32,
pixel_height: u32,
coordinate: RawTextCoordinate,
) -> Result<[f64; 6]> {
image_transform(page, pixel_size(pixel_width, pixel_height)?, coordinate)
}
pub(super) fn source_page_geometry(size: [u16; 2]) -> Result<EmpiricalPageGeometry> {
Ok(EmpiricalPageGeometry {
origin_points: [0.0, 0.0],
size: source_size(size)?,
})
}
pub(super) fn mode_zero_page_geometry(size: [u16; 2]) -> Result<EmpiricalPageGeometry> {
let mut page = source_page_geometry(size)?;
page.size.width_points += 10.0;
page.size.height_points += 10.0;
Ok(page)
}
pub(super) fn source_image_transform(
page: EmpiricalPageGeometry,
coordinate: RawTextCoordinate,
) -> Result<[f64; 6]> {
image_transform(
page,
source_size([coordinate.width, coordinate.height])?,
coordinate,
)
}
fn source_size([width, height]: [u16; 2]) -> Result<PageSpec> {
if width == 0 || height == 0 {
return Err(Error::invalid(
"declared source dimensions must be positive",
));
}
Ok(PageSpec {
width_points: f64::from(width) * EMPIRICAL_COORDINATE_POINTS_PER_UNIT,
height_points: f64::from(height) * EMPIRICAL_COORDINATE_POINTS_PER_UNIT,
})
}
fn image_transform(
page: EmpiricalPageGeometry,
size: PageSpec,
coordinate: RawTextCoordinate,
) -> Result<[f64; 6]> {
let [left, bottom, _, top] = page.media_box()?;
let x_offset = f64::from(coordinate.x) * EMPIRICAL_COORDINATE_POINTS_PER_UNIT;
let y_offset = f64::from(coordinate.y) * EMPIRICAL_COORDINATE_POINTS_PER_UNIT;
let x = left + x_offset;
let y = top - y_offset;
if ((x - left) - x_offset).abs() > EMPIRICAL_PLACEMENT_TOLERANCE_POINTS
|| ((top - y) - y_offset).abs() > EMPIRICAL_PLACEMENT_TOLERANCE_POINTS
|| ((y - bottom) - (page.size.height_points - y_offset)).abs()
> EMPIRICAL_PLACEMENT_TOLERANCE_POINTS
{
return Err(Error::invalid(
"empirical PDF origin cannot preserve the selected coordinate precision",
));
}
Ok([size.width_points, 0.0, 0.0, -size.height_points, x, y])
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum C8GlyphClass {
Cjk,
Latin,
}
pub(super) fn native_glyph_transform(
page: EmpiricalPageGeometry,
source_origin: [u16; 2],
position: [u16; 2],
style: u16,
class: C8GlyphClass,
axes: [Option<u16>; 2],
) -> Result<[f64; 6]> {
let [left, _, _, top] = page.media_box()?;
let (width, height, latin_offset) = if axes != [None; 2] {
let (width, height, baseline) = match axes {
[Some(1), Some(1)] if class == C8GlyphClass::Cjk => (1.0, 1.0, 0.0),
[Some(4), Some(4)] => (4.0, 4.0, 15.0),
[Some(22), Some(22)] => (22.0, 22.0, 11.0),
[Some(34), Some(34)] => (34.0, 34.0, 8.0),
[Some(36), Some(36)] => (36.0, 36.0, 8.0),
[Some(38), Some(38)] => (38.0, 38.0, 7.0),
[Some(40), Some(40)] => (40.0, 40.0, 7.0),
[Some(width @ (28 | 43)), Some(height @ (28 | 43))] => (
f64::from(width),
f64::from(height),
if height == 28 { 9.0 } else { 6.0 },
),
_ => {
return Err(Error::invalid("unverified native explicit glyph axes"));
}
};
(width * 75.0 / 301.0, height * 75.0 / 301.0, baseline)
} else if matches!(style, 0x1000 | 0x1001 | 0x1020 | 0x1021 | 0x1022 | 0x1041) {
let width = match (style >> 5) & 31 {
0 => 21.0,
1 => 24.0,
_ => 28.0,
};
let (height, baseline) = match style & 31 {
0 => (21.0, 11.0),
1 => (24.0, 10.0),
_ => (28.0, 9.0),
};
(width * 75.0 / 301.0, height * 75.0 / 301.0, baseline)
} else if style == 0x096b && class == C8GlyphClass::Cjk {
(95.0 * 75.0 / 301.0, 95.0 * 75.0 / 301.0, 0.0)
} else if matches!(style, 0xe58c | 0x114a | 0x154a | 0xb94c) && class == C8GlyphClass::Cjk {
let width = if style == 0xe58c { 109.0 } else { 84.0 };
let height = if matches!(style, 0xe58c | 0xb94c) {
109.0
} else {
84.0
};
(width * 75.0 / 301.0, height * 75.0 / 301.0, 0.0)
} else {
if !matches!(style & 0xfc00, 0x0800 | 0x0c00 | 0x1000)
&& !matches!(
style,
0x04e7 | 0x14e7 | 0x0484 | 0x1484 | 0x9c84 | 0x04c6 | 0x14c6 | 0x14a5 | 0xa4a5
)
{
return Err(Error::invalid("unverified C8 glyph style flags"));
}
c8_style_metrics((style & 0x03ff) | 0x1000)?
};
let unit = EMPIRICAL_COORDINATE_POINTS_PER_UNIT;
let mut x = left + (f64::from(position[0]) - f64::from(source_origin[0]) + 20.0) * unit;
let mut y = top - (f64::from(position[1]) - f64::from(source_origin[1]) - 15.0) * unit - height;
if class == C8GlyphClass::Latin {
x += width / 8.0;
y -= latin_offset * unit;
}
Ok([width, 0.0, 0.0, height, x, y])
}
pub(super) fn native_digit_transform(
page: EmpiricalPageGeometry,
source_origin: [u16; 2],
position: [u16; 2],
style: u16,
axes: [Option<u16>; 2],
) -> Result<[f64; 6]> {
let mut transform = native_glyph_transform(
page,
source_origin,
position,
style,
C8GlyphClass::Latin,
axes,
)?;
let [left, _, _, top] = page.media_box()?;
let unit = EMPIRICAL_COORDINATE_POINTS_PER_UNIT;
transform[4] = left + (f64::from(position[0]) - f64::from(source_origin[0]) + 20.0) * unit;
transform[5] =
top - (f64::from(position[1]) - f64::from(source_origin[1])) * unit - transform[3];
Ok(transform)
}
pub(super) fn native_letter_transform(
page: EmpiricalPageGeometry,
source_origin: [u16; 2],
position: [u16; 2],
style: u16,
axes: [Option<u16>; 2],
code: u16,
) -> Result<[f64; 6]> {
const UPPER: [u32; 26] = [
8, 8, 8, 8, 10, 8, 8, 8, 17, 17, 8, 10, 5, 8, 8, 9, 8, 8, 11, 8, 6, 6, 1, 7, 7, 9,
];
const LOWER: [u32; 26] = [
15, 13, 15, 13, 15, 17, 13, 13, 20, 20, 13, 20, 4, 14, 14, 13, 13, 18, 17, 18, 14, 12, 7,
13, 13, 13,
];
let factor = match code {
0xa0c1..=0xa0da => UPPER[usize::from(code - 0xa0c1)],
0xa0e1..=0xa0fa => LOWER[usize::from(code - 0xa0e1)],
_ => return Err(Error::invalid("unverified native alphabet code")),
};
let mut transform = native_glyph_transform(
page,
source_origin,
position,
style,
C8GlyphClass::Latin,
axes,
)?;
let width = (transform[0] * 301.0 / 75.0).round() as u32;
let height = (transform[3] * 301.0 / 75.0).round() as u32;
let x_offset = 20 + (width * factor * 5 + 64) / 128;
let y_offset = (height * 30 + 64) / 128;
let [left, _, _, top] = page.media_box()?;
let unit = EMPIRICAL_COORDINATE_POINTS_PER_UNIT;
transform[4] =
left + (f64::from(position[0]) - f64::from(source_origin[0]) + f64::from(x_offset)) * unit;
transform[5] = top
- (f64::from(position[1]) - f64::from(source_origin[1]) - f64::from(y_offset)) * unit
- transform[3];
Ok(transform)
}
pub(super) fn mode_zero_glyph_transform(
page: EmpiricalPageGeometry,
source_origin: [u16; 2],
position: [u16; 2],
style: u16,
class: C8GlyphClass,
axes: [Option<u16>; 2],
) -> Result<[f64; 6]> {
if axes == [None; 2] && matches!(style, 0 | 0x1000) {
let [left, _, _, top] = page.media_box()?;
let unit = EMPIRICAL_COORDINATE_POINTS_PER_UNIT;
let em = 21.0 * 75.0 / 301.0;
let baseline = if class == C8GlyphClass::Latin {
10.0
} else {
0.0
};
return Ok([
em,
0.0,
0.0,
em,
left + (f64::from(position[0]) - f64::from(source_origin[0]) + 20.0) * unit,
top - (f64::from(position[1]) - f64::from(source_origin[1]) + baseline) * unit - em,
]);
}
if !matches!(
style,
0 | 0x0484 | 0x0884 | 0x1084 | 0x9c84 | 0x0ca4 | 0x10a4 | 0x10a5 | 0x04e7 | 0x0ce7
) && !(class == C8GlyphClass::Cjk && style == 0x154a)
{
return Err(Error::invalid("unverified HN-B mode-0 glyph style"));
}
let mut transform = native_glyph_transform(
page,
source_origin,
position,
style,
C8GlyphClass::Cjk,
axes,
)?;
let baseline = if class == C8GlyphClass::Latin {
10.0
} else {
0.0
};
transform[5] -= (15.0 + baseline) * EMPIRICAL_COORDINATE_POINTS_PER_UNIT;
Ok(transform)
}
pub(super) fn mode_zero_digit_transform(
page: EmpiricalPageGeometry,
source_origin: [u16; 2],
position: [u16; 2],
style: u16,
axes: [Option<u16>; 2],
) -> Result<[f64; 6]> {
let (left, down) = match (style, axes) {
(_, [Some(36), Some(36)]) => (22.0, 18.0),
(0 | 0x1000, [None, None]) => (21.0, 15.0),
(0x0484 | 0x0884 | 0x1084 | 0x9c84 | 0x0ca4 | 0x10a4, [None, None]) => (22.0, 18.0),
(0x10a5, [None, None]) => (22.0, 20.0),
_ => {
return Err(Error::invalid("unverified HN-B mode-0 digit geometry"));
}
};
let mut transform = mode_zero_glyph_transform(
page,
source_origin,
position,
style,
C8GlyphClass::Cjk,
axes,
)?;
transform[4] -= left * EMPIRICAL_COORDINATE_POINTS_PER_UNIT;
transform[5] -= down * EMPIRICAL_COORDINATE_POINTS_PER_UNIT;
Ok(transform)
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct EmpiricalC8HorizontalDecoration {
pub first_glyph: [f64; 6],
pub clip: [f64; 4],
pub glyph_count: u16,
}
pub fn empirical_c8_horizontal_decoration(
page: EmpiricalPageGeometry,
source_origin: [u16; 2],
points: [[u16; 2]; 2],
style: u16,
axes: [Option<u16>; 2],
) -> Result<EmpiricalC8HorizontalDecoration> {
let [left, bottom, _, top] = page.media_box()?;
let (width, height) = match axes {
[None, None]
if (2..=8).contains(&((style >> 5) & 31)) && (2..=8).contains(&(style & 31)) =>
{
let (width, height, _) = c8_style_metrics(style)?;
(width, height)
}
[Some(size @ (34 | 40)), Some(height)] if size == height => {
let em = f64::from(size) * 75.0 / 301.0;
(em, em)
}
_ => return Err(Error::invalid("unverified C8 decoration dimensions")),
};
let [[x1, y1], [x2, y2]] = points;
if x2 <= x1 || y1 != y2 {
return Err(Error::invalid(
"unverified C8 decoration direction or empty span",
));
}
let unit = EMPIRICAL_COORDINATE_POINTS_PER_UNIT;
let x = left + (f64::from(x1) - f64::from(source_origin[0])) * unit;
let y = top - (f64::from(y1) - f64::from(source_origin[1])) * unit - height / 2.0;
let span = f64::from(x2 - x1) * unit;
let glyph_count = (span / width).ceil() as u16;
Ok(EmpiricalC8HorizontalDecoration {
first_glyph: [width, 0.0, 0.0, height, x, y],
clip: [x, bottom, span, page.size.height_points],
glyph_count,
})
}
pub fn empirical_c8_segment(
page: EmpiricalPageGeometry,
source_origin: [u16; 2],
points: [[u16; 2]; 2],
style: u16,
) -> Result<[[f64; 2]; 2]> {
let [left, _, _, top] = page.media_box()?;
if !matches!(
style,
0xa380 | 0xa381 | 0xa382 | 0xa383 | 0xa385 | 0xa387 | 0xa38b | 0xa38d
) {
return Err(Error::invalid("unverified C8 segment style"));
}
if matches!(style, 0xa387 | 0xa38d) && points.iter().flatten().any(|word| word & 0xc000 != 0) {
return Err(Error::invalid("unverified C8 segment coordinate flags"));
}
let unit = EMPIRICAL_COORDINATE_POINTS_PER_UNIT;
let mut points = points;
if style == 0xa385 && points[0][0] & 0xc000 == 0xc000 {
points[0][0] &= 0x3fff;
}
Ok(points.map(|[x, y]| {
[
left + (f64::from(x) - f64::from(source_origin[0]) + 20.0) * unit,
top - (f64::from(y) - f64::from(source_origin[1]) + 20.0) * unit,
]
}))
}
fn c8_style_metrics(style: u16) -> Result<(f64, f64, f64)> {
if style & 0xfc00 != 0x1000 {
return Err(Error::invalid("unverified C8 glyph style flags"));
}
let metrics = |field| -> Result<(f64, f64)> {
let (step, latin_offset) = match field {
2 => (28, 9),
3 => (31, 9),
4 => (35, 8),
5 => (42, 6),
6 => (48, 5),
7 => (56, 3),
8 => (63, 1),
9 => (72, -1),
10 => (84, -4),
_ => {
return Err(Error::invalid("unverified C8 glyph size field"));
}
};
Ok((f64::from(step) * 75.0 / 301.0, f64::from(latin_offset)))
};
let (width, _) = metrics((style >> 5) & 31)?;
let (height, latin_offset) = metrics(style & 31)?;
Ok((width, height, latin_offset))
}
fn pixel_size(pixel_width: u32, pixel_height: u32) -> Result<PageSpec> {
if pixel_width == 0 || pixel_height == 0 {
return Err(Error::invalid(
"empirical image dimensions must be positive",
));
}
Ok(PageSpec {
width_points: f64::from(pixel_width) * 72.0 / 300.0,
height_points: f64::from(pixel_height) * 72.0 / 300.0,
})
}
#[cfg(test)]
mod tests;