Skip to main content

TextPage

Struct TextPage 

Source
pub struct TextPage {
    pub chars: Vec<CharBox>,
    pub search_text: Vec<char>,
    pub runs: IndexMap,
    pub fonts: BTreeMap<ObjectIndex, String>,
}
Expand description

One page’s extracted text (ISO 32000-1 §14.8.2).

Holds two sequences that are not the same, and conflating them is the easiest way to get this crate wrong:

  • chars, addressed by CharIndex, is every character the page drew or the extractor invented, geometry attached. It keeps control characters, \0 for an unmappable code, and U+0002 where a word was hyphenated across a line.
  • search_text, addressed by TextIndex, is what a search matches and a selection copies. It drops the control characters and the placeholders, expands ligatures the character stream keeps whole, and carries U+00AD at a hyphenated break and U+FFFD for an unmappable code, so it can disagree with chars position by position.

runs converts between the two spaces; every signature names which one it counts in. Cheap to clone and Send + Sync, so a document’s pages can be extracted in parallel.

§Examples

The fields are public, so a page can be built by hand — which is how the query side is exercised without a file:

use pdfrum_text::TextPage;

let page = TextPage {
    search_text: "Hello, world!".chars().collect(),
    ..TextPage::default()
};
// `Display` writes the search-facing text, never the character stream.
assert_eq!(page.to_string(), "Hello, world!");
// …which is a different sequence, and here an empty one.
assert_eq!(page.char_count(), 0);

Fields§

§chars: Vec<CharBox>

Characters in reading order addressed by CharIndex.

§search_text: Vec<char>

Normalized search-facing text addressed by TextIndex.

§runs: IndexMap

Map between CharIndex and TextIndex.

§fonts: BTreeMap<ObjectIndex, String>

The base font name of each text object whose font has one, by the ObjectIndex its characters carry. A Type 3 font has no base name and is absent. Read through a character with font_name.

Implementations§

Source§

impl TextPage

Source

pub fn words(&self) -> Vec<Word>

The page’s words in reading order: the runs of chars between whitespace, each with the box its glyphs cover and the font of its first character.

A word’s text is what slice gives for its range, so a hyphenated line break shows up as two words, the first ending in U+00AD. A run whose characters all vanish from the search text — the charcode-0 passthrough, the control code points — is not a word.

§Examples
let page = TextPage::default();
assert!(page.words().is_empty());
Source§

impl TextPage

Source

pub fn char_count(&self) -> usize

How many characters the page drew.

This counts chars, not search_text: the two sequences have different lengths, and a page can have text and no characters or the reverse.

§Examples
use pdfrum_text::TextPage;

let page = TextPage {
    search_text: "Hello".chars().collect(),
    ..TextPage::default()
};
assert_eq!(page.to_string().len(), 5);
assert_eq!(page.char_count(), 0);
Source

pub fn slice(&self, range: impl RangeBounds<CharIndex>) -> String

A run of the search_text, addressed in CharIndex.

The bounds are widened onto real text, not filtered: a start on a character the text does not hold scans forward to the next one it does, and an end on one scans back. Characters inside the range are never skipped. So on control_characters.pdf, asking for the fifteen characters from character 17 returns "Goodbye, world!" even though the text itself has no character 17.

This is the one call where the two spaces of TextPage meet: the bounds count characters and the answer is text.

§Examples

A page holding "Hello, world!" on one line and "Goodbye, world!" on the next, with the extractor’s generated \r\n between them:

let at = CharIndex::new;
assert_eq!(page.slice(at(0)..at(5)), "Hello");
// An unbounded end is "to the end of the page".
assert_eq!(page.slice(at(15)..), "Goodbye, world!");
assert_eq!(page.slice(..), "Hello, world!\r\nGoodbye, world!");
Source

pub fn find<'a>( &'a self, needle: &str, options: FindOptions, ) -> impl Iterator<Item = Range<TextIndex>> + 'a

Searches the page.

Match ranges are TextIndex offsets into search_textnot the CharIndex space web_links reports; see TextPage.

§Examples
let page = TextPage {
    search_text: "Hello, world!".chars().collect(),
    ..TextPage::default()
};
let at = TextIndex::new;
let hits: Vec<_> = page.find("world", FindOptions::default()).collect();
assert_eq!(hits, [at(7)..at(12)]);
// The default is case-insensitive.
let hits: Vec<_> = page.find("WORLD", FindOptions::default()).collect();
assert_eq!(hits, [at(7)..at(12)]);

Every web and mail address in the page’s text.

Reported ranges are CharIndex spans into charsnot the TextIndex space find returns. The two index spaces are different sequences; see TextPage.

§Examples

The fixture below draws no address, so nothing is reported; a page that draws www.example.com reports it with an http:// already prefixed.

assert!(page.web_links().is_empty());
Source

pub fn rects(&self, range: impl RangeBounds<CharIndex>) -> Vec<Rect>

The boxes covering a run of CharIndex, one per run of consecutive characters sharing a text object.

Generated characters and boxes under 0.01 in either dimension are skipped, and a box is pushed unconditionally at the end — so a run in which every character was skipped still yields one box, an all-zero rectangle. An unbounded end is “to the end of the page”, and a range running past the end takes what is there.

§Examples

Two lines set in two different fonts are two text objects, so the whole page yields two boxes rather than one:

let boxes = page.rects(..);
assert_eq!(boxes.len(), 2);
// The first line sits below the second in page space, which is y-up.
assert!(boxes[0].y1 < boxes[1].y0);

// A run inside one object is one box.
assert_eq!(page.rects(CharIndex::new(0)..CharIndex::new(5)).len(), 1);
Source

pub fn index_at(&self, point: Point, tolerance: Size) -> Option<CharIndex>

The character under a point in page space, or the nearest within tolerance. A point inside a character’s box wins outright and reports the first such character; failing that, and only when a tolerance is given, the nearest character within it.

§Examples
// Inside the first glyph's box.
assert_eq!(page.index_at(Point::new(24.0, 54.0), Size::ZERO), Some(CharIndex::new(0)));
// Far from every glyph, with no tolerance to fall back on.
assert_eq!(page.index_at(Point::new(500.0, 500.0), Size::ZERO), None);
Source

pub fn text_in_rect(&self, rect: Rect) -> String

The text inside a rectangle, with \r\n where the selection crosses a baseline.

§Examples
// A rectangle covering only the lower line takes only its text.
assert_eq!(page.text_in_rect(Rect::new(0.0, 0.0, 200.0, 70.0)), "Hello, world!");
Source

pub fn text_of_object(&self, object: ObjectIndex) -> String

The text drawn by a specific text object. The ObjectIndex is the one a character carries in CharBox::object, counting text objects in content order.

§Examples
assert_eq!(page.text_of_object(ObjectIndex(0)), "Hello, world!");
assert_eq!(page.text_of_object(ObjectIndex(1)), "Goodbye, world!");
// An object the page does not have draws nothing.
assert_eq!(page.text_of_object(ObjectIndex(9)), "");
Source

pub fn char(&self, index: CharIndex) -> Result<&CharBox, Error>

Returns the character box at the given CharIndex.

§Errors

Returns Error::CharIndexOutOfRange when index is past the end of chars.

§Examples
let first = page.char(CharIndex::new(0))?;
assert_eq!(char::from_u32(first.unicode), Some('H'));
assert_eq!(first.char_type, CharType::Normal);
assert_eq!(first.font_size, 12.0);

// The error names both the index and the bound it broke.
assert_eq!(
    page.char(CharIndex::new(999)),
    Err(Error::CharIndexOutOfRange { index: CharIndex::new(999), len: 30 }),
);
Source

pub fn font_name(&self, index: CharIndex) -> Option<&str>

The base font name of the font the character at index was drawn with, as the font crate normalized it — the subset tag stripped and a standard-14 alias canonicalized, so ABCDEF+Arial,Bold reads as Helvetica-Bold.

None past the end, for a character no text object drew (every generated one), and for a font with no base name (Type 3).

§Examples
// Two lines, two fonts.
assert_eq!(page.font_name(CharIndex::new(0)), Some("Times-Roman"));
assert_eq!(page.font_name(CharIndex::new(15)), Some("Helvetica"));

// Character 13 is the line break the extractor generated: no text
// object drew it, so it has no font.
assert_eq!(page.font_name(CharIndex::new(13)), None);

Trait Implementations§

Source§

impl Clone for TextPage

Source§

fn clone(&self) -> TextPage

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for TextPage

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for TextPage

Source§

fn default() -> TextPage

Returns the “default value” for a type. Read more
Source§

impl Display for TextPage

Formats the page as its search_textnot the chars stream, which holds the control characters and placeholders this drops.

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.