Skip to main content

livre/content/operators/text/
showing.rs

1//! Text-showing operators. See section 9.4.3 of the PDF specification.
2
3use std::fmt::Display;
4
5use enum_dispatch::enum_dispatch;
6use winnow::{combinator::peek, dispatch, token::any, BStr, PResult, Parser};
7
8use crate::{
9    content::state::TextObject,
10    extraction::{extract, Extract, HexadecimalString, LiteralString, PDFString},
11};
12
13use super::TextOperation;
14
15/// Abstraction over any text-showing operator, as defined in section 9.4.3 of the PDF
16/// specification.
17#[derive(Debug, Clone, PartialEq)]
18#[enum_dispatch(TextOperation)]
19pub enum TextShowingOperator {
20    /// The `Tj` operator. Show a text string.
21    ShowText(ShowText),
22    /// The `'` operator. Move to the next line and show a text string.
23    MoveToNextLineAndShowText(MoveToNextLineAndShowText),
24    /// The `"` operator. Move to the next line and show a text string, using `aw` as the word
25    MoveToNextLineAndShowTextWithSpacing(MoveToNextLineAndShowTextWithSpacing),
26    /// The `TJ` operator. Show zero or more text strings, allowing individual glyph positioning.
27    ShowTextArray(ShowTextArray),
28}
29
30impl Display for TextShowingOperator {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        match self {
33            TextShowingOperator::ShowText(v) => write!(f, "{}", v),
34            TextShowingOperator::MoveToNextLineAndShowText(v) => writeln!(f, "{}", v),
35            TextShowingOperator::MoveToNextLineAndShowTextWithSpacing(v) => writeln!(f, "{}", v),
36            TextShowingOperator::ShowTextArray(v) => write!(f, "{}", v),
37        }
38    }
39}
40
41/// `Tj` operator. Show a text string.
42///
43/// ```raw
44/// <0052> Tj
45/// ```
46#[derive(Debug, Clone, PartialEq, Extract)]
47pub struct ShowText(PDFString);
48
49impl Display for ShowText {
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51        write!(f, "{}", self.0)
52    }
53}
54
55impl TextOperation for ShowText {
56    fn apply(self, text_object: &mut TextObject) {
57        text_object.add_text(self.0);
58    }
59}
60
61/// `'` operator. Move to the next line and show a text string.
62///
63/// Equivalent to:
64///
65/// ```raw
66/// T*
67/// string Tj
68/// ```
69#[derive(Debug, Clone, PartialEq, Extract)]
70pub struct MoveToNextLineAndShowText(PDFString);
71
72impl TextOperation for MoveToNextLineAndShowText {
73    fn apply(self, text_object: &mut TextObject) {
74        text_object.move_to_next_line();
75        text_object.add_text(self.0);
76    }
77}
78
79impl Display for MoveToNextLineAndShowText {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        write!(f, "{}", self.0)
82    }
83}
84
85/// `"` operator.
86///
87/// Move to the next line and show a text string, using `aw` as the word spacing and
88/// `ac` as the character spacing (setting the corresponding parameters in the text state).
89/// `aw` and `ac` shall be numbers expressed in unscaled text space units.
90#[derive(Debug, Clone, PartialEq, Extract)]
91pub struct MoveToNextLineAndShowTextWithSpacing(f32, f32, PDFString);
92
93impl TextOperation for MoveToNextLineAndShowTextWithSpacing {
94    fn apply(self, text_object: &mut TextObject) {
95        let MoveToNextLineAndShowTextWithSpacing(aw, ac, text) = self;
96
97        text_object.move_to_next_line();
98        text_object.set_word_spacing(aw);
99        text_object.set_character_spacing(ac);
100        text_object.add_text(text);
101    }
102}
103
104impl Display for MoveToNextLineAndShowTextWithSpacing {
105    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106        write!(f, "{}", self.0)
107    }
108}
109
110/// `TJ` operator.
111///
112/// Show zero or more text strings, allowing individual glyph positioning.
113/// Each element of the array is either a string or a number:
114///
115/// - in the case of a string, the operator shows the text;
116/// - in the case of a number, the operator adjust the text position by that
117///   amount (i.e. translate the text matrix). Expressed in thousandths of text
118///   space units. That amount is subtracted from the current "selected coordinate",
119///   depending on the writing mode.
120///
121/// ```raw
122/// [(5)-6(1)-6(,)-2( )-2(A)] TJ
123/// ```
124#[derive(Debug, Clone, PartialEq, Extract)]
125pub struct ShowTextArray(Vec<TextArrayElement>);
126
127impl TextOperation for ShowTextArray {
128    fn apply(self, text_object: &mut TextObject) {
129        text_object.add_text_array(self.0);
130    }
131}
132
133impl Display for ShowTextArray {
134    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135        for element in &self.0 {
136            if let TextArrayElement::Text(v) = element {
137                write!(f, "{}", v)?;
138            }
139        }
140        Ok(())
141    }
142}
143
144/// Helper enumeration to represent the elements of a text array.
145#[derive(Debug, Clone, PartialEq)]
146pub enum TextArrayElement {
147    Text(PDFString),
148    Offset(f32),
149}
150
151impl From<PDFString> for TextArrayElement {
152    fn from(value: PDFString) -> Self {
153        Self::Text(value)
154    }
155}
156
157impl From<&str> for TextArrayElement {
158    fn from(value: &str) -> Self {
159        Self::Text(value.into())
160    }
161}
162
163impl From<HexadecimalString> for TextArrayElement {
164    fn from(value: HexadecimalString) -> Self {
165        Self::Text(value.into())
166    }
167}
168
169impl From<LiteralString> for TextArrayElement {
170    fn from(value: LiteralString) -> Self {
171        Self::Text(value.into())
172    }
173}
174
175impl From<f32> for TextArrayElement {
176    fn from(value: f32) -> Self {
177        Self::Offset(value)
178    }
179}
180
181impl Extract<'_> for TextArrayElement {
182    fn extract(input: &mut &BStr) -> PResult<Self> {
183        dispatch! {peek(any);
184            b'(' => LiteralString::extract.map(TextArrayElement::from),
185            b'<' => HexadecimalString::extract.map(TextArrayElement::from),
186            _ => extract.map(TextArrayElement::Offset),
187        }
188        .parse_next(input)
189    }
190}
191
192#[cfg(test)]
193mod tests {
194    use std::fmt::Debug;
195
196    use super::TextArrayElement::*;
197    use super::*;
198
199    use indoc::indoc;
200    use rstest::rstest;
201
202    #[rstest]
203    #[case(
204        indoc!{br#"
205            [ (&''!\(\)) 7 (*+) -4 (,) -8 (-) 6 (!\(.) 3 (-) -7 (.\(/) 3 ] TJ
206        "#},
207        ShowTextArray(vec![
208            "&''!()".into(),
209            Offset(7.0),
210            "*+".into(),
211            Offset(-4.0),
212            ",".into(),
213            Offset(-8.0),
214            "-".into(),
215            Offset(6.0),
216            "!(.".into(),
217            Offset(3.0),
218            "-".into(),
219            Offset(-7.0),
220            ".(/".into(),
221            Offset(3.0),
222        ])
223    )]
224    fn extraction<'de, T>(#[case] input: &'de [u8], #[case] expected: T)
225    where
226        T: Extract<'de> + Debug + PartialEq,
227    {
228        let result = extract(&mut input.as_ref()).unwrap();
229        assert_eq!(expected, result);
230    }
231}