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
|
||||
// See Notices.txt for copyright information
|
||||
|
||||
use non_nan_float::NonNaNF32;
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
collections::{HashMap, HashSet},
|
||||
fmt,
|
||||
sync::OnceLock,
|
||||
};
|
||||
|
||||
mod quad_tree;
|
||||
mod xml_tree;
|
||||
|
||||
mod non_nan_float {
|
||||
#[derive(Default, PartialEq, PartialOrd, Clone, Copy)]
|
||||
|
|
@ -44,6 +52,14 @@ mod non_nan_float {
|
|||
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 {
|
||||
|
|
@ -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() {}
|
||||
|
|
|
|||
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