22
33namespace PrinsFrank \PdfParser \Document \ContentStream \PositionedText ;
44
5- use PrinsFrank \PdfParser \Document \Dictionary \ DictionaryValue \ Name \ EncodingNameValue ;
5+ use PrinsFrank \PdfParser \Document \ContentStream \ PositionedText \ TextSegment \ TextSegment ;
66use PrinsFrank \PdfParser \Document \Document ;
7- use PrinsFrank \PdfParser \Document \Generic \Character \LiteralStringEscapeCharacter ;
87use PrinsFrank \PdfParser \Document \Object \Decorator \Font ;
98use PrinsFrank \PdfParser \Document \Object \Decorator \Page ;
109use PrinsFrank \PdfParser \Exception \ParseFailureException ;
1110
1211readonly class PositionedTextElement {
12+ /** @param list<TextSegment> $textSegments */
1313 public function __construct (
14- public string $ rawTextContent ,
14+ public array $ textSegments ,
1515 public TransformationMatrix $ absoluteMatrix ,
1616 public TextState $ textState ,
1717 ) {}
@@ -27,48 +27,11 @@ public function getFont(Document $document, Page $page): Font {
2727
2828 /** @throws ParseFailureException */
2929 public function getText (Document $ document , Page $ page ): string {
30- if (($ result = preg_match_all ('/(?<chars>(<( \\\\>|[^>])*>)|(\(( \\\\\)|[^)])*\)))(?<offset>-?[0-9]+(\.[0-9]+)?)?/ ' , $ this ->rawTextContent , $ matches , PREG_SET_ORDER )) === false ) {
31- throw new ParseFailureException (sprintf ('Error with regex ' ));
32- } elseif ($ result === 0 ) {
33- throw new ParseFailureException (sprintf ('Operands "%s" is not in a recognized format ' , $ this ->rawTextContent ));
34- }
35-
36- $ string = '' ;
3730 $ font = $ this ->getFont ($ document , $ page );
38- foreach ($ matches as $ match ) {
39- if (str_starts_with ($ match ['chars ' ], '( ' ) && str_ends_with ($ match ['chars ' ], ') ' )) {
40- $ unescapedChars = LiteralStringEscapeCharacter::unescapeCharacters (substr ($ match ['chars ' ], 1 , -1 ));
41- if (preg_match ('/^ \\\\\d{3}$/ ' , substr ($ match ['chars ' ], 1 , -1 )) === 1 && ($ glyph = $ font ->getDifferences ()?->getGlyph((int ) octdec (substr ($ match ['chars ' ], 2 , -1 )))) !== null ) {
42- $ chars = $ glyph ->getChar ();
43- } elseif (strlen ($ unescapedChars ) === 1 && ($ glyph = $ font ->getDifferences ()?->getGlyph(ord ($ unescapedChars ))) !== null ) {
44- $ chars = $ glyph ->getChar ();
45- } elseif (in_array ($ encoding = $ font ->getEncoding (), [EncodingNameValue::MacExpertEncoding, EncodingNameValue::WinAnsiEncoding], true ) && $ font ->getDifferences () === null ) {
46- $ chars = $ encoding ->decodeString ($ unescapedChars );
47- } elseif (($ toUnicodeCMap = $ font ->getToUnicodeCMap () ?? $ font ->getToUnicodeCMapDescendantFont ()) !== null ) {
48- $ chars = $ toUnicodeCMap ->textToUnicode (bin2hex ($ unescapedChars ));
49- } elseif ($ encoding !== null ) {
50- $ chars = $ encoding ->decodeString ($ unescapedChars );
51- } else {
52- $ chars = $ unescapedChars ;
53- }
54-
55- $ string .= $ chars ;
56- } elseif (str_starts_with ($ match ['chars ' ], '< ' ) && str_ends_with ($ match ['chars ' ], '> ' )) {
57- $ chars = substr ($ match ['chars ' ], 1 , -1 );
58- if (($ toUnicodeCMap = $ font ->getToUnicodeCMap () ?? $ font ->getToUnicodeCMapDescendantFont ()) !== null ) {
59- $ string .= $ toUnicodeCMap ->textToUnicode ($ chars );
60- } elseif (($ encoding = $ font ->getEncoding ()) !== null ) {
61- $ string .= $ encoding ->decodeString (implode ('' , array_map (fn (string $ character ) => mb_chr ((int ) hexdec ($ character )), str_split ($ chars , 2 ))));
62- } else {
63- $ string .= EncodingNameValue::IdentityH->decodeString ($ chars );
64- }
65- } else {
66- throw new ParseFailureException (sprintf ('Unrecognized character group format "%s" ' , $ match ['chars ' ]));
67- }
6831
69- if ( isset ( $ match [ ' offset ' ]) && ( float ) $ match [ ' offset ' ] < - 100 ) {
70- $ string .= ' ' ;
71- }
32+ $ string = '' ;
33+ foreach ( $ this -> textSegments as $ textSegment ) {
34+ $ string .= $ textSegment -> getText ( $ font );
7235 }
7336
7437 return $ string ;
@@ -77,27 +40,8 @@ public function getText(Document $document, Page $page): string {
7740 /** @return list<int> */
7841 public function getCodePoints (): array {
7942 $ codePoints = [];
80- if (($ result = preg_match_all ('/(?<chars>(<( \\\\>|[^>])*>)|(\(( \\\\\)|[^)])*\)))(?<offset>-?[0-9]+(\.[0-9]+)?)?/ ' , $ this ->rawTextContent , $ matches , PREG_SET_ORDER )) === false ) {
81- throw new ParseFailureException (sprintf ('Error with regex ' ));
82- } elseif ($ result === 0 ) {
83- throw new ParseFailureException (sprintf ('Operands "%s" is not in a recognized format ' , $ this ->rawTextContent ));
84- }
85-
86- foreach ($ matches as $ match ) {
87- if (str_starts_with ($ match ['chars ' ], '( ' ) && str_ends_with ($ match ['chars ' ], ') ' )) {
88- $ chars = str_replace (['\( ' , '\) ' , '\n ' , '\r ' ], ['( ' , ') ' , "\n" , "\r" ], substr ($ match ['chars ' ], 1 , -1 ));
89- $ chars = preg_replace_callback ('/ \\\\([0-7]{3})/ ' , fn (array $ matches ) => mb_chr ((int ) octdec ($ matches [1 ])), $ chars )
90- ?? throw new ParseFailureException ();
91- foreach (str_split ($ chars ) as $ char ) {
92- $ codePoints [] = ord ($ char );
93- }
94- } elseif (str_starts_with ($ match ['chars ' ], '< ' ) && str_ends_with ($ match ['chars ' ], '> ' )) {
95- foreach (str_split (substr ($ match ['chars ' ], 1 , -1 ), 4 ) as $ char ) {
96- $ codePoints [] = is_int ($ codePoint = hexdec ($ char )) ? $ codePoint : throw new ParseFailureException ();
97- }
98- } else {
99- throw new ParseFailureException (sprintf ('Unrecognized character group format "%s" ' , $ match ['chars ' ]));
100- }
43+ foreach ($ this ->textSegments as $ textSegment ) {
44+ array_push ($ codePoints , ...$ textSegment ->getCodePoints ());
10145 }
10246
10347 return $ codePoints ;
0 commit comments