port more to rust
This commit is contained in:
parent
8643d47338
commit
104ee37933
2 changed files with 776 additions and 0 deletions
578
src/main.rs
578
src/main.rs
|
|
@ -1,8 +1,16 @@
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
use non_nan_float::NonNaNF32;
|
use non_nan_float::NonNaNF32;
|
||||||
|
use std::{
|
||||||
|
borrow::Borrow,
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
fmt,
|
||||||
|
sync::OnceLock,
|
||||||
|
};
|
||||||
|
|
||||||
mod quad_tree;
|
mod quad_tree;
|
||||||
|
mod xml_tree;
|
||||||
|
|
||||||
mod non_nan_float {
|
mod non_nan_float {
|
||||||
#[derive(Default, PartialEq, PartialOrd, Clone, Copy)]
|
#[derive(Default, PartialEq, PartialOrd, Clone, Copy)]
|
||||||
|
|
@ -44,6 +52,14 @@ mod non_nan_float {
|
||||||
self.partial_cmp(other).expect("known to be non-NaN")
|
self.partial_cmp(other).expect("known to be non-NaN")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::ops::Neg for NonNaNF32 {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn neg(self) -> Self::Output {
|
||||||
|
Self(-self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! make_enum_font {
|
macro_rules! make_enum_font {
|
||||||
|
|
@ -514,4 +530,566 @@ impl Font {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
struct Char {
|
||||||
|
font: Font,
|
||||||
|
text: String,
|
||||||
|
adv: NonNaNF32,
|
||||||
|
min_x: NonNaNF32,
|
||||||
|
min_y: NonNaNF32,
|
||||||
|
max_x: NonNaNF32,
|
||||||
|
max_y: NonNaNF32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Char {
|
||||||
|
fn width(&self) -> f32 {
|
||||||
|
self.max_x.get() - self.min_x.get()
|
||||||
|
}
|
||||||
|
fn height(&self) -> f32 {
|
||||||
|
self.max_y.get() - self.min_y.get()
|
||||||
|
}
|
||||||
|
fn top_down_left_to_right_sort_key(&self) -> impl Ord {
|
||||||
|
(-self.min_y, self.min_x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const COLUMN_SPLIT_X: f32 = 300.0;
|
||||||
|
const PAGE_BODY_MAX_X: f32 = 600.0;
|
||||||
|
const PAGE_BODY_MIN_X: f32 = 50.0;
|
||||||
|
const PAGE_BODY_MAX_Y: f32 = 780.0;
|
||||||
|
const PAGE_BODY_MIN_Y: f32 = 45.0;
|
||||||
|
const ONE_TITLE_LINE_SPLIT_Y: f32 = 734.0;
|
||||||
|
const TWO_TITLE_LINES_SPLIT_Y: f32 = 715.0;
|
||||||
|
const INSN_BIT_FIELDS_PREFIX_TEXT_TOP_PAD_HEIGHT: f32 = 29.938;
|
||||||
|
const INSN_BIT_FIELDS_AFFIX_TEXT_TO_BOX_TOP_HEIGHT: f32 = 9.278;
|
||||||
|
const INSN_BIT_FIELDS_PREFIX_BOX_BOTTOM_TO_SUFFIX_TEXT_HEIGHT: f32 = 20.971;
|
||||||
|
const INSN_BIT_FIELDS_TOP_PAD_HEIGHT: f32 = 20.175;
|
||||||
|
const INSN_BIT_FIELDS_TOP_PAD_HEIGHT2: f32 = 14.694;
|
||||||
|
const INSN_BIT_FIELDS_BOX_HEIGHT: f32 = 22.317;
|
||||||
|
const INSN_SP_REGS_ALTERED_REGISTER_COLUMN_X: f32 = 34.405;
|
||||||
|
const INSN_SP_REGS_ALTERED_FIELDS_COLUMN_X: f32 = 86.692;
|
||||||
|
const INSN_SP_REGS_ALTERED_FIELDS_CONDS_SPLIT_X: f32 = 188.74;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct ParsedTextLine {
|
||||||
|
element: xml_tree::Element,
|
||||||
|
regular_min_y: f32,
|
||||||
|
regular_max_y: f32,
|
||||||
|
fonts: TextLineFonts,
|
||||||
|
chars: Vec<Char>,
|
||||||
|
preceding_blank_lines: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParsedTextLine {
|
||||||
|
fn regular_height(&self) -> f32 {
|
||||||
|
self.regular_max_y - self.regular_min_y
|
||||||
|
}
|
||||||
|
fn get_header_text(&self) -> Option<String> {
|
||||||
|
assert_eq!(self.fonts, TextLineFonts::InsnDescFonts);
|
||||||
|
if !self.element.text.trim().is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if !self.element.tail.trim().is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let [b] = &*self.element.children else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
if b.tag.normal() != Some("b") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if b.children.len() != 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let text = self.element.inner_text();
|
||||||
|
// should also check titlecase, but rust doesn't include that in std
|
||||||
|
if text.ends_with(":") && text.chars().next().is_some_and(|ch| ch.is_uppercase()) {
|
||||||
|
Some(text)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn write_xml(&self, parent: &mut xml_tree::Element, trailing_nl: bool) {
|
||||||
|
for _ in 0..self.preceding_blank_lines {
|
||||||
|
parent.sub_element("br".into(), []).tail = "\n".into();
|
||||||
|
}
|
||||||
|
if let Some(last_child) = parent.children.last_mut() {
|
||||||
|
last_child.tail += &self.element.text;
|
||||||
|
} else {
|
||||||
|
parent.text += &self.element.text;
|
||||||
|
}
|
||||||
|
parent.children.extend_from_slice(&self.element.children);
|
||||||
|
if trailing_nl {
|
||||||
|
parent.sub_element("br".into(), []).tail = "\n".into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn write_xml_lines(
|
||||||
|
lines: impl IntoIterator<Item: Borrow<ParsedTextLine>>,
|
||||||
|
parent: &mut xml_tree::Element,
|
||||||
|
trailing_nl: bool,
|
||||||
|
preceding_nl: bool,
|
||||||
|
) {
|
||||||
|
if preceding_nl {
|
||||||
|
parent.sub_element("br".into(), []).tail = "\n".into();
|
||||||
|
}
|
||||||
|
let mut first = true;
|
||||||
|
for line in lines {
|
||||||
|
let line = line.borrow();
|
||||||
|
if first {
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
parent.sub_element("br".into(), []).tail = "\n".into();
|
||||||
|
}
|
||||||
|
line.write_xml(parent, false);
|
||||||
|
}
|
||||||
|
if trailing_nl {
|
||||||
|
parent.sub_element("br".into(), []).tail = "\n".into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for ParsedTextLine {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let Self {
|
||||||
|
element,
|
||||||
|
regular_min_y,
|
||||||
|
regular_max_y,
|
||||||
|
fonts,
|
||||||
|
chars,
|
||||||
|
preceding_blank_lines,
|
||||||
|
} = self;
|
||||||
|
f.debug_struct("ParsedTextLine")
|
||||||
|
.field("element", &format_args!("{element}"))
|
||||||
|
.field("regular_min_y", regular_min_y)
|
||||||
|
.field("regular_max_y", regular_max_y)
|
||||||
|
.field("fonts", fonts)
|
||||||
|
.field("chars", chars)
|
||||||
|
.field("preceding_blank_lines", preceding_blank_lines)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ParsedTextLine {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
for _ in 0..self.preceding_blank_lines {
|
||||||
|
f.write_str("\n")?;
|
||||||
|
}
|
||||||
|
self.element.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
enum BaselinePos {
|
||||||
|
Above,
|
||||||
|
Below,
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! make_enum_with_values {
|
||||||
|
(
|
||||||
|
$(#[$enum_meta:meta])*
|
||||||
|
enum $Enum:ident {
|
||||||
|
$($Variant:ident,)*
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
$(#[$enum_meta])*
|
||||||
|
enum $Enum {
|
||||||
|
$($Variant,)*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl $Enum {
|
||||||
|
const VALUES: &[Self] = &[$(Self::$Variant,)*];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
make_enum_with_values! {
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
enum TextLineFonts {
|
||||||
|
InsnMnemonicFonts,
|
||||||
|
InsnHeaderFonts,
|
||||||
|
InsnBitFieldBitNumberFonts,
|
||||||
|
InsnBitFieldNameFonts,
|
||||||
|
InsnBitFieldsAffixTitleFonts,
|
||||||
|
InsnCodeFonts,
|
||||||
|
InsnDescFonts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextLineFonts {
|
||||||
|
fn regular(self) -> &'static [Font] {
|
||||||
|
match self {
|
||||||
|
TextLineFonts::InsnMnemonicFonts => KnownFontGroup::InsnDesc.fonts(),
|
||||||
|
TextLineFonts::InsnHeaderFonts => &[Font::InsnHeader],
|
||||||
|
TextLineFonts::InsnBitFieldBitNumberFonts => &[Font::InsnDescSmall, Font::TitlePageRev],
|
||||||
|
TextLineFonts::InsnBitFieldNameFonts => KnownFontGroup::InsnDesc.fonts(),
|
||||||
|
TextLineFonts::InsnBitFieldsAffixTitleFonts => &[Font::InsnDescSmall],
|
||||||
|
TextLineFonts::InsnCodeFonts => KnownFontGroup::InsnCode.fonts(),
|
||||||
|
TextLineFonts::InsnDescFonts => {
|
||||||
|
static FONTS: OnceLock<Box<[Font]>> = OnceLock::new();
|
||||||
|
FONTS.get_or_init(|| {
|
||||||
|
Box::from_iter(
|
||||||
|
KnownFontGroup::InsnDesc
|
||||||
|
.fonts()
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.chain([Font::InsnDescSmall]),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn italic(self) -> Option<&'static [Font]> {
|
||||||
|
match self {
|
||||||
|
TextLineFonts::InsnMnemonicFonts => None,
|
||||||
|
TextLineFonts::InsnHeaderFonts => None,
|
||||||
|
TextLineFonts::InsnBitFieldBitNumberFonts => None,
|
||||||
|
TextLineFonts::InsnBitFieldNameFonts => None,
|
||||||
|
TextLineFonts::InsnBitFieldsAffixTitleFonts => None,
|
||||||
|
TextLineFonts::InsnCodeFonts => None,
|
||||||
|
TextLineFonts::InsnDescFonts => {
|
||||||
|
Some(&[Font::InsnDescItalic, Font::InsnDescSmallItalic])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn bold(self) -> Option<&'static [Font]> {
|
||||||
|
match self {
|
||||||
|
TextLineFonts::InsnMnemonicFonts => None,
|
||||||
|
TextLineFonts::InsnHeaderFonts => None,
|
||||||
|
TextLineFonts::InsnBitFieldBitNumberFonts => None,
|
||||||
|
TextLineFonts::InsnBitFieldNameFonts => None,
|
||||||
|
TextLineFonts::InsnBitFieldsAffixTitleFonts => Some(&[Font::InsnDescSmallBold]),
|
||||||
|
TextLineFonts::InsnCodeFonts => None,
|
||||||
|
TextLineFonts::InsnDescFonts => Some(&[Font::InsnDescBold, Font::InsnDescSmallBold]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn bold_italic(self) -> Option<&'static [Font]> {
|
||||||
|
match self {
|
||||||
|
TextLineFonts::InsnMnemonicFonts => None,
|
||||||
|
TextLineFonts::InsnHeaderFonts => None,
|
||||||
|
TextLineFonts::InsnBitFieldBitNumberFonts => None,
|
||||||
|
TextLineFonts::InsnBitFieldNameFonts => None,
|
||||||
|
TextLineFonts::InsnBitFieldsAffixTitleFonts => None,
|
||||||
|
TextLineFonts::InsnCodeFonts => None,
|
||||||
|
TextLineFonts::InsnDescFonts => {
|
||||||
|
Some(&[Font::InsnDescBoldItalic, Font::InsnDescSmallBoldItalic])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn subscript(self) -> Option<&'static [Font]> {
|
||||||
|
match self {
|
||||||
|
TextLineFonts::InsnMnemonicFonts => None,
|
||||||
|
TextLineFonts::InsnHeaderFonts => None,
|
||||||
|
TextLineFonts::InsnBitFieldBitNumberFonts => None,
|
||||||
|
TextLineFonts::InsnBitFieldNameFonts => Some(&[Font::InsnDescSubscript]),
|
||||||
|
TextLineFonts::InsnBitFieldsAffixTitleFonts => None,
|
||||||
|
TextLineFonts::InsnCodeFonts => Some(KnownFontGroup::InsnCodeSubscript.fonts()),
|
||||||
|
TextLineFonts::InsnDescFonts => Some(&[Font::InsnDescSubscript]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn bold_subscript(self) -> Option<&'static [Font]> {
|
||||||
|
match self {
|
||||||
|
TextLineFonts::InsnMnemonicFonts => None,
|
||||||
|
TextLineFonts::InsnHeaderFonts => None,
|
||||||
|
TextLineFonts::InsnBitFieldBitNumberFonts => None,
|
||||||
|
TextLineFonts::InsnBitFieldNameFonts => None,
|
||||||
|
TextLineFonts::InsnBitFieldsAffixTitleFonts => None,
|
||||||
|
TextLineFonts::InsnCodeFonts => None,
|
||||||
|
TextLineFonts::InsnDescFonts => Some(&[Font::InsnDescBoldSubscript]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn italic_subscript(self) -> Option<&'static [Font]> {
|
||||||
|
match self {
|
||||||
|
TextLineFonts::InsnMnemonicFonts => None,
|
||||||
|
TextLineFonts::InsnHeaderFonts => None,
|
||||||
|
TextLineFonts::InsnBitFieldBitNumberFonts => None,
|
||||||
|
TextLineFonts::InsnBitFieldNameFonts => None,
|
||||||
|
TextLineFonts::InsnBitFieldsAffixTitleFonts => None,
|
||||||
|
TextLineFonts::InsnCodeFonts => None,
|
||||||
|
TextLineFonts::InsnDescFonts => Some(&[Font::InsnDescItalicSubscript]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn bold_italic_subscript(self) -> Option<&'static [Font]> {
|
||||||
|
match self {
|
||||||
|
TextLineFonts::InsnMnemonicFonts => None,
|
||||||
|
TextLineFonts::InsnHeaderFonts => None,
|
||||||
|
TextLineFonts::InsnBitFieldBitNumberFonts => None,
|
||||||
|
TextLineFonts::InsnBitFieldNameFonts => None,
|
||||||
|
TextLineFonts::InsnBitFieldsAffixTitleFonts => None,
|
||||||
|
TextLineFonts::InsnCodeFonts => None,
|
||||||
|
TextLineFonts::InsnDescFonts => Some(&[Font::InsnDescBoldItalicSubscript]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn code(self) -> Option<&'static [Font]> {
|
||||||
|
match self {
|
||||||
|
TextLineFonts::InsnMnemonicFonts => None,
|
||||||
|
TextLineFonts::InsnHeaderFonts => None,
|
||||||
|
TextLineFonts::InsnBitFieldBitNumberFonts => None,
|
||||||
|
TextLineFonts::InsnBitFieldNameFonts => None,
|
||||||
|
TextLineFonts::InsnBitFieldsAffixTitleFonts => None,
|
||||||
|
TextLineFonts::InsnCodeFonts => None,
|
||||||
|
TextLineFonts::InsnDescFonts => Some(&[Font::InsnDescCode, Font::InsnExtMnemonic]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn code_subscript(self) -> Option<&'static [Font]> {
|
||||||
|
match self {
|
||||||
|
TextLineFonts::InsnMnemonicFonts => None,
|
||||||
|
TextLineFonts::InsnHeaderFonts => None,
|
||||||
|
TextLineFonts::InsnBitFieldBitNumberFonts => None,
|
||||||
|
TextLineFonts::InsnBitFieldNameFonts => None,
|
||||||
|
TextLineFonts::InsnBitFieldsAffixTitleFonts => None,
|
||||||
|
TextLineFonts::InsnCodeFonts => None,
|
||||||
|
TextLineFonts::InsnDescFonts => Some(KnownFontGroup::InsnCodeSubscript.fonts()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn get_fonts(
|
||||||
|
self,
|
||||||
|
part_kind: TextLineFontKind,
|
||||||
|
) -> Option<(&'static [Font], Option<BaselinePos>)> {
|
||||||
|
let fonts = match part_kind {
|
||||||
|
TextLineFontKind::Regular => self.regular(),
|
||||||
|
TextLineFontKind::Italic => self.italic()?,
|
||||||
|
TextLineFontKind::Bold => self.bold()?,
|
||||||
|
TextLineFontKind::BoldItalic => self.bold_italic()?,
|
||||||
|
TextLineFontKind::Subscript => self.subscript()?,
|
||||||
|
TextLineFontKind::Superscript => self.subscript()?,
|
||||||
|
TextLineFontKind::BoldSubscript => self.bold_subscript()?,
|
||||||
|
TextLineFontKind::BoldSuperscript => self.bold_subscript()?,
|
||||||
|
TextLineFontKind::ItalicSubscript => self.italic_subscript()?,
|
||||||
|
TextLineFontKind::ItalicSuperscript => self.italic_subscript()?,
|
||||||
|
TextLineFontKind::BoldItalicSubscript => self.bold_italic_subscript()?,
|
||||||
|
TextLineFontKind::BoldItalicSuperscript => self.bold_italic_subscript()?,
|
||||||
|
TextLineFontKind::Code => self.code()?,
|
||||||
|
TextLineFontKind::CodeSubscript => self.code_subscript()?,
|
||||||
|
TextLineFontKind::CodeSuperscript => self.code_subscript()?,
|
||||||
|
};
|
||||||
|
Some((fonts, part_kind.sub_super().baseline_pos()))
|
||||||
|
}
|
||||||
|
fn font_to_kind_map(self) -> &'static HashMap<(Font, Option<BaselinePos>), TextLineFontKind> {
|
||||||
|
static MAPS: OnceLock<
|
||||||
|
HashMap<TextLineFonts, HashMap<(Font, Option<BaselinePos>), TextLineFontKind>>,
|
||||||
|
> = OnceLock::new();
|
||||||
|
&MAPS.get_or_init(|| {
|
||||||
|
Self::VALUES
|
||||||
|
.iter()
|
||||||
|
.map(|&this: &TextLineFonts| {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
for &kind in TextLineFontKind::VALUES {
|
||||||
|
let Some((fonts, baseline_pos)) = this.get_fonts(kind) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
for font in fonts {
|
||||||
|
let old_kind = map.insert((font.clone(), baseline_pos), kind);
|
||||||
|
assert!(
|
||||||
|
old_kind.is_none(),
|
||||||
|
"duplicate font: kind={kind:?} old_kind={old_kind:?} font={font:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(this, map)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
})[&self]
|
||||||
|
}
|
||||||
|
fn fonts(self) -> &'static HashSet<Font> {
|
||||||
|
static SETS: OnceLock<HashMap<TextLineFonts, HashSet<Font>>> = OnceLock::new();
|
||||||
|
&SETS.get_or_init(|| {
|
||||||
|
Self::VALUES
|
||||||
|
.iter()
|
||||||
|
.map(|&this: &TextLineFonts| {
|
||||||
|
let mut set = HashSet::new();
|
||||||
|
for &kind in TextLineFontKind::VALUES {
|
||||||
|
let Some((fonts, _baseline_pos)) = this.get_fonts(kind) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
set.extend(fonts.iter().cloned());
|
||||||
|
}
|
||||||
|
(this, set)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
})[&self]
|
||||||
|
}
|
||||||
|
fn get_kind(self, font: Font, baseline_pos: BaselinePos) -> Option<TextLineFontKind> {
|
||||||
|
let font_to_kind_map = self.font_to_kind_map();
|
||||||
|
font_to_kind_map
|
||||||
|
.get(&(font.clone(), Some(baseline_pos)))
|
||||||
|
.or_else(|| font_to_kind_map.get(&(font, None)))
|
||||||
|
.copied()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||||
|
enum FontVariantCode {
|
||||||
|
Code,
|
||||||
|
NotCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FontVariantCode {
|
||||||
|
const fn value(self) -> &'static [&'static str] {
|
||||||
|
match self {
|
||||||
|
Self::Code => &["code"],
|
||||||
|
Self::NotCode => &[],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||||
|
enum FontVariantBold {
|
||||||
|
Bold,
|
||||||
|
NotBold,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FontVariantBold {
|
||||||
|
const fn value(self) -> &'static [&'static str] {
|
||||||
|
match self {
|
||||||
|
Self::Bold => &["b"],
|
||||||
|
Self::NotBold => &[],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||||
|
enum FontVariantItalic {
|
||||||
|
Italic,
|
||||||
|
NotItalic,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FontVariantItalic {
|
||||||
|
const fn value(self) -> &'static [&'static str] {
|
||||||
|
match self {
|
||||||
|
Self::Italic => &["i"],
|
||||||
|
Self::NotItalic => &[],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||||
|
enum FontVariantSubSuper {
|
||||||
|
NotSubSuper,
|
||||||
|
Subscript,
|
||||||
|
Superscript,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FontVariantSubSuper {
|
||||||
|
const fn value(self) -> &'static [&'static str] {
|
||||||
|
match self {
|
||||||
|
Self::NotSubSuper => &[],
|
||||||
|
Self::Subscript => &["sub"],
|
||||||
|
Self::Superscript => &["sup"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FontVariantSubSuper {
|
||||||
|
fn baseline_pos(self) -> Option<BaselinePos> {
|
||||||
|
match self {
|
||||||
|
FontVariantSubSuper::NotSubSuper => None,
|
||||||
|
FontVariantSubSuper::Subscript => Some(BaselinePos::Below),
|
||||||
|
FontVariantSubSuper::Superscript => Some(BaselinePos::Above),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
make_enum_with_values! {
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||||
|
enum TextLineFontKind {
|
||||||
|
Regular,
|
||||||
|
Subscript,
|
||||||
|
Superscript,
|
||||||
|
Italic,
|
||||||
|
ItalicSubscript,
|
||||||
|
ItalicSuperscript,
|
||||||
|
Bold,
|
||||||
|
BoldSubscript,
|
||||||
|
BoldSuperscript,
|
||||||
|
BoldItalic,
|
||||||
|
BoldItalicSubscript,
|
||||||
|
BoldItalicSuperscript,
|
||||||
|
Code,
|
||||||
|
CodeSubscript,
|
||||||
|
CodeSuperscript,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextLineFontKind {
|
||||||
|
fn code(self) -> FontVariantCode {
|
||||||
|
match self {
|
||||||
|
Self::Regular
|
||||||
|
| Self::Subscript
|
||||||
|
| Self::Superscript
|
||||||
|
| Self::Italic
|
||||||
|
| Self::ItalicSubscript
|
||||||
|
| Self::ItalicSuperscript
|
||||||
|
| Self::Bold
|
||||||
|
| Self::BoldSubscript
|
||||||
|
| Self::BoldSuperscript
|
||||||
|
| Self::BoldItalic
|
||||||
|
| Self::BoldItalicSubscript
|
||||||
|
| Self::BoldItalicSuperscript => FontVariantCode::NotCode,
|
||||||
|
Self::Code | Self::CodeSubscript | Self::CodeSuperscript => FontVariantCode::Code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn bold(self) -> FontVariantBold {
|
||||||
|
match self {
|
||||||
|
Self::Regular
|
||||||
|
| Self::Subscript
|
||||||
|
| Self::Superscript
|
||||||
|
| Self::Italic
|
||||||
|
| Self::ItalicSubscript
|
||||||
|
| Self::ItalicSuperscript => FontVariantBold::NotBold,
|
||||||
|
Self::Bold
|
||||||
|
| Self::BoldSubscript
|
||||||
|
| Self::BoldSuperscript
|
||||||
|
| Self::BoldItalic
|
||||||
|
| Self::BoldItalicSubscript
|
||||||
|
| Self::BoldItalicSuperscript => FontVariantBold::Bold,
|
||||||
|
Self::Code | Self::CodeSubscript | Self::CodeSuperscript => FontVariantBold::NotBold,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn italic(self) -> FontVariantItalic {
|
||||||
|
match self {
|
||||||
|
Self::Regular | Self::Subscript | Self::Superscript => FontVariantItalic::NotItalic,
|
||||||
|
Self::Italic | Self::ItalicSubscript | Self::ItalicSuperscript => {
|
||||||
|
FontVariantItalic::Italic
|
||||||
|
}
|
||||||
|
Self::Bold | Self::BoldSubscript | Self::BoldSuperscript => {
|
||||||
|
FontVariantItalic::NotItalic
|
||||||
|
}
|
||||||
|
Self::BoldItalic | Self::BoldItalicSubscript | Self::BoldItalicSuperscript => {
|
||||||
|
FontVariantItalic::Italic
|
||||||
|
}
|
||||||
|
Self::Code | Self::CodeSubscript | Self::CodeSuperscript => {
|
||||||
|
FontVariantItalic::NotItalic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn sub_super(self) -> FontVariantSubSuper {
|
||||||
|
match self {
|
||||||
|
Self::Regular => FontVariantSubSuper::NotSubSuper,
|
||||||
|
Self::Subscript => FontVariantSubSuper::Subscript,
|
||||||
|
Self::Superscript => FontVariantSubSuper::Superscript,
|
||||||
|
Self::Italic => FontVariantSubSuper::NotSubSuper,
|
||||||
|
Self::ItalicSubscript => FontVariantSubSuper::Subscript,
|
||||||
|
Self::ItalicSuperscript => FontVariantSubSuper::Superscript,
|
||||||
|
Self::Bold => FontVariantSubSuper::NotSubSuper,
|
||||||
|
Self::BoldSubscript => FontVariantSubSuper::Subscript,
|
||||||
|
Self::BoldSuperscript => FontVariantSubSuper::Superscript,
|
||||||
|
Self::BoldItalic => FontVariantSubSuper::NotSubSuper,
|
||||||
|
Self::BoldItalicSubscript => FontVariantSubSuper::Subscript,
|
||||||
|
Self::BoldItalicSuperscript => FontVariantSubSuper::Superscript,
|
||||||
|
Self::Code => FontVariantSubSuper::NotSubSuper,
|
||||||
|
Self::CodeSubscript => FontVariantSubSuper::Subscript,
|
||||||
|
Self::CodeSuperscript => FontVariantSubSuper::Superscript,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn text_line_tags(self) -> impl Clone + Iterator<Item = &'static str> {
|
||||||
|
self.code()
|
||||||
|
.value()
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.chain(self.bold().value().iter().copied())
|
||||||
|
.chain(self.italic().value().iter().copied())
|
||||||
|
.chain(self.sub_super().value().iter().copied())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
|
||||||
198
src/xml_tree.rs
Normal file
198
src/xml_tree.rs
Normal file
|
|
@ -0,0 +1,198 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
|
use quick_xml::{
|
||||||
|
Writer,
|
||||||
|
events::{BytesText, Event},
|
||||||
|
};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
struct FmtToIoAdaptor<W> {
|
||||||
|
writer: W,
|
||||||
|
partial_char: [u8; 4],
|
||||||
|
partial_char_len: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: fmt::Write> FmtToIoAdaptor<W> {
|
||||||
|
fn new(writer: W) -> Self {
|
||||||
|
Self {
|
||||||
|
writer,
|
||||||
|
partial_char: [0; 4],
|
||||||
|
partial_char_len: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn finish(self) -> Result<W, fmt::Error> {
|
||||||
|
let Self {
|
||||||
|
writer,
|
||||||
|
partial_char: _,
|
||||||
|
partial_char_len,
|
||||||
|
} = self;
|
||||||
|
if partial_char_len != 0 {
|
||||||
|
Err(fmt::Error)
|
||||||
|
} else {
|
||||||
|
Ok(writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn write_byte(&mut self, b: u8) -> std::io::Result<()> {
|
||||||
|
let Self {
|
||||||
|
writer,
|
||||||
|
partial_char,
|
||||||
|
partial_char_len,
|
||||||
|
} = self;
|
||||||
|
partial_char[usize::from(*partial_char_len)] = b;
|
||||||
|
*partial_char_len += 1;
|
||||||
|
match str::from_utf8(&partial_char[..usize::from(*partial_char_len)]) {
|
||||||
|
Ok(s) => {
|
||||||
|
*partial_char_len = 0;
|
||||||
|
writer.write_str(s).map_err(std::io::Error::other)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
if e.error_len().is_some() {
|
||||||
|
*partial_char_len = 0;
|
||||||
|
Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: fmt::Write> std::io::Write for FmtToIoAdaptor<W> {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||||
|
for &b in buf {
|
||||||
|
self.write_byte(b)?;
|
||||||
|
}
|
||||||
|
Ok(buf.len())
|
||||||
|
}
|
||||||
|
fn flush(&mut self) -> std::io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub(crate) enum ElementTag {
|
||||||
|
Comment,
|
||||||
|
Normal(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ElementTag {
|
||||||
|
pub(crate) fn normal(&self) -> Option<&str> {
|
||||||
|
match self {
|
||||||
|
ElementTag::Comment => None,
|
||||||
|
ElementTag::Normal(v) => Some(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for ElementTag {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Comment => f.write_str("<Comment>"),
|
||||||
|
Self::Normal(v) => v.fmt(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// like python's xml.etree.ElementTree.Element
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct Element {
|
||||||
|
pub(crate) tag: ElementTag,
|
||||||
|
pub(crate) attrib: Vec<(String, String)>,
|
||||||
|
/// text contained in this element but before any children
|
||||||
|
pub(crate) text: String,
|
||||||
|
pub(crate) children: Vec<Element>,
|
||||||
|
/// text after the end of this element
|
||||||
|
pub(crate) tail: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// equivalent to python `xml.etree.ElementTree.tostring(self, encoding="unicode")`
|
||||||
|
impl fmt::Display for Element {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let mut writer = Writer::new(FmtToIoAdaptor::new(f));
|
||||||
|
fn helper(
|
||||||
|
element: &Element,
|
||||||
|
writer: &mut Writer<impl std::io::Write>,
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
let Element {
|
||||||
|
tag,
|
||||||
|
attrib,
|
||||||
|
text,
|
||||||
|
children,
|
||||||
|
tail,
|
||||||
|
} = element;
|
||||||
|
match tag {
|
||||||
|
ElementTag::Comment => {
|
||||||
|
writer.write_event(Event::Comment(BytesText::new(text)))?;
|
||||||
|
}
|
||||||
|
ElementTag::Normal(tag) if tag.is_empty() => {
|
||||||
|
writer.write_event(Event::Text(BytesText::new(text)))?;
|
||||||
|
}
|
||||||
|
ElementTag::Normal(tag) => {
|
||||||
|
let mut element_writer = writer.create_element(tag);
|
||||||
|
for (name, value) in attrib {
|
||||||
|
element_writer =
|
||||||
|
element_writer.with_attribute((name.as_str(), value.as_str()));
|
||||||
|
}
|
||||||
|
if text.is_empty() && children.is_empty() {
|
||||||
|
element_writer.write_empty()?;
|
||||||
|
} else {
|
||||||
|
element_writer.write_inner_content(|writer| {
|
||||||
|
writer.write_event(Event::Text(BytesText::new(text)))?;
|
||||||
|
for child in children {
|
||||||
|
helper(child, writer)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writer.write_event(Event::Text(BytesText::new(tail)))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
helper(self, &mut writer).map_err(|_| fmt::Error)?;
|
||||||
|
writer.into_inner().finish()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Element {
|
||||||
|
/// equivalent to python `"".join(self.itertext())`
|
||||||
|
pub(crate) fn inner_text(&self) -> String {
|
||||||
|
let mut retval = String::new();
|
||||||
|
fn helper(element: &Element, retval: &mut String) {
|
||||||
|
let Element {
|
||||||
|
tag,
|
||||||
|
attrib: _,
|
||||||
|
text,
|
||||||
|
children,
|
||||||
|
tail: _,
|
||||||
|
} = element;
|
||||||
|
let ElementTag::Normal(_) = tag else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
retval.push_str(text);
|
||||||
|
for child in children {
|
||||||
|
helper(child, retval);
|
||||||
|
retval.push_str(&child.tail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
helper(self, &mut retval);
|
||||||
|
retval
|
||||||
|
}
|
||||||
|
/// equivalent of python's `xml.etree.ElementTree.SubElement()`
|
||||||
|
pub(crate) fn sub_element(
|
||||||
|
&mut self,
|
||||||
|
tag: String,
|
||||||
|
attrib: impl IntoIterator<Item = (String, String)>,
|
||||||
|
) -> &mut Self {
|
||||||
|
self.children.push(Self {
|
||||||
|
tag: ElementTag::Normal(tag),
|
||||||
|
attrib: Vec::from_iter(attrib),
|
||||||
|
text: String::new(),
|
||||||
|
children: Vec::new(),
|
||||||
|
tail: String::new(),
|
||||||
|
});
|
||||||
|
self.children.last_mut().expect("just pushed")
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue