Skip to content

Commit 597b5c1

Browse files
committed
Add more operations, add text encoding / decoding module
1 parent e0e46c3 commit 597b5c1

File tree

12 files changed

+484
-198
lines changed

12 files changed

+484
-198
lines changed

.github/workflows/ci.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ jobs:
3636
with:
3737
platform: "${{ contains(matrix.target-sys, 'x86_64') && 'x64' || 'i686' }}"
3838
version: 13.2.0
39+
continue-on-error: true
3940
- name: Install toolchain for ${{ matrix.target-sys }}-${{ matrix.target-abi }}
4041
uses: dtolnay/rust-toolchain@stable
4142
with:

build.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
pub fn main() {
2-
if std::env::var("TARGET").expect("Unable to get TARGET").contains("wasm") {
2+
if std::env::var("TARGET")
3+
.expect("Unable to get TARGET")
4+
.contains("wasm")
5+
{
36
println!("cargo:rustc-cfg=feature=\"wasm\"");
47
}
58
}

index.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,10 @@
8989
</div>
9090
<div class="output-panel">
9191
<div class="pdf-viewer-controls">
92-
<button id="prev-page">< Previous Page</button>
92+
<button id="prev-page">< Page</button>
9393
<input type="number" id="page-number" value="1" min="1">
94-
<button id="next-page">Next Page ></button>
95-
<button id="save-pdf">Save PDF</button>
94+
<button id="next-page">Next ></button>
95+
<button id="save-pdf">Save</button>
9696
</div>
9797
<div class="pdf-viewer-wrapper">
9898
<div class="pdf-viewer-c1">

script.js

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -235,16 +235,14 @@ function applySignatureToPage(page, resources) {
235235
if (!signatureImageBase64) return page;
236236

237237
const signatureImageId = 'user-signature-image'; // Unique ID for signature image
238-
if (!resources.xobjects.map[signatureImageId]) {
239-
resources.xobjects.map[signatureImageId] = { // Simplified XObject structure for demo
240-
subtype: "Image",
241-
image_data: signatureImageBase64, // Assuming base64 image data is directly usable
242-
width: 200, // Placeholder, adjust based on actual image
243-
height: 100, // Placeholder, adjust based on actual image
244-
color_space: "DeviceRGB", // Or determine from image
245-
bits_per_component: 8
246-
};
247-
}
238+
resources.xobjects.map[signatureImageId] = { // Simplified XObject structure for demo
239+
subtype: "Image",
240+
image_data: signatureImageBase64, // Assuming base64 image data is directly usable
241+
width: 200, // Placeholder, adjust based on actual image
242+
height: 100, // Placeholder, adjust based on actual image
243+
color_space: "DeviceRGB", // Or determine from image
244+
bits_per_component: 8
245+
};
248246

249247
const signatureX = parseFloat(document.getElementById('signature-x').value);
250248
const signatureY = parseFloat(document.getElementById('signature-y').value);

src/deserialize.rs

Lines changed: 76 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ use serde_derive::{Deserialize, Serialize};
1515
use crate::{
1616
BuiltinFont, Color, DictItem, ExtendedGraphicsStateId, ExtendedGraphicsStateMap, FontId,
1717
LineDashPattern, LinePoint, Op, PageAnnotMap, ParsedFont, PdfDocument, PdfDocumentInfo,
18-
PdfFontMap, PdfLayerMap, PdfMetadata, PdfPage, PdfResources, PolygonRing, RawImage, TextMatrix,
19-
TextRenderingMode, XObject, XObjectId, XObjectMap,
18+
PdfFontMap, PdfLayerMap, PdfMetadata, PdfPage, PdfResources, PolygonRing, RawImage,
19+
RenderingIntent, TextMatrix, TextRenderingMode, XObject, XObjectId, XObjectMap,
2020
conformance::PdfConformance,
2121
date::{OffsetDateTime, UtcOffset},
2222
};
@@ -682,8 +682,6 @@ pub fn parse_op(
682682
}
683683
out_ops.push(Op::RestoreGraphicsState);
684684
}
685-
686-
/*
687685
"ri" => {
688686
if op.operands.len() != 1 {
689687
warnings.push(PdfWarnMsg::error(
@@ -704,9 +702,25 @@ pub fn parse_op(
704702
return Ok(Vec::new());
705703
}
706704
};
705+
let intent = match intent.as_str() {
706+
"AbsoluteColorimetric" => RenderingIntent::AbsoluteColorimetric,
707+
"RelativeColorimetric" => RenderingIntent::RelativeColorimetric,
708+
"Saturation" => RenderingIntent::Saturation,
709+
"Perceptual" => RenderingIntent::Perceptual,
710+
other => {
711+
warnings.push(PdfWarnMsg::error(
712+
page,
713+
op_id,
714+
format!(
715+
"Unknown rendering intent '{}', defaulting to RelativeColorimetric",
716+
other
717+
),
718+
));
719+
RenderingIntent::RelativeColorimetric
720+
}
721+
};
707722
out_ops.push(Op::SetRenderingIntent { intent });
708723
}
709-
*/
710724
"rg" => {
711725
// 'rg' sets fill color in RGB.
712726
if op.operands.len() == 3 {
@@ -864,9 +878,61 @@ pub fn parse_op(
864878
}
865879
}
866880

867-
// --- Text transformation ---
881+
"T*" => {
882+
out_ops.push(Op::AddLineBreak);
883+
}
884+
"TL" => {
885+
if op.operands.len() == 1 {
886+
let val = to_f32(&op.operands[0]);
887+
out_ops.push(Op::SetLineHeight { lh: Pt(val) });
888+
} else {
889+
warnings.push(PdfWarnMsg::error(
890+
page,
891+
op_id,
892+
format!("Warning: 'TL' expects 1 operand, got {}", op.operands.len()),
893+
));
894+
}
895+
}
896+
"Ts" => {
897+
if op.operands.len() == 1 {
898+
let rise = to_f32(&op.operands[0]);
899+
out_ops.push(Op::SetLineOffset { multiplier: rise });
900+
} else {
901+
warnings.push(PdfWarnMsg::error(
902+
page,
903+
op_id,
904+
format!("Warning: 'Ts' expects 1 operand, got {}", op.operands.len()),
905+
));
906+
}
907+
}
908+
"Tz" => {
909+
if op.operands.len() == 1 {
910+
let scale_percent = to_f32(&op.operands[0]);
911+
out_ops.push(Op::SetHorizontalScaling {
912+
percent: scale_percent,
913+
});
914+
} else {
915+
warnings.push(PdfWarnMsg::error(
916+
page,
917+
op_id,
918+
format!("Warning: 'Tz' expects 1 operand, got {}", op.operands.len()),
919+
));
920+
}
921+
}
922+
"TD" => {
923+
if op.operands.len() == 2 {
924+
let tx = to_f32(&op.operands[0]);
925+
let ty = to_f32(&op.operands[1]);
926+
out_ops.push(Op::MoveTextCursorAndSetLeading { tx, ty });
927+
} else {
928+
warnings.push(PdfWarnMsg::error(
929+
page,
930+
op_id,
931+
format!("'TD' expects 2 operands, got {}", op.operands.len()),
932+
));
933+
}
934+
}
868935
"Tm" => {
869-
// 'Tm' sets the text matrix. It takes 6 operands.
870936
if op.operands.len() == 6 {
871937
let a = to_f32(&op.operands[0]);
872938
let b = to_f32(&op.operands[1]);
@@ -953,20 +1019,21 @@ pub fn parse_op(
9531019
});
9541020
}
9551021

956-
// --- Text mode begin/end ---
9571022
"BT" => {
1023+
// --- Text mode begin
9581024
state.in_text_mode = true;
9591025
state.current_font = None;
9601026
state.current_font_size = None;
9611027
out_ops.push(Op::StartTextSection);
9621028
}
9631029
"ET" => {
1030+
// --- Text mode end
9641031
state.in_text_mode = false;
9651032
out_ops.push(Op::EndTextSection);
9661033
}
9671034

968-
// --- Font + size (Tf) ---
9691035
"Tf" => {
1036+
// --- Font + size (Tf) ---
9701037
if op.operands.len() == 2 {
9711038
if let Some(font_name) = as_name(&op.operands[0]) {
9721039
state.current_font = Some(crate::FontId(font_name));

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use serde_derive::{Deserialize, Serialize};
66

77
/// Link / bookmark annotation handling
88
pub mod annotation;
9+
pub mod text;
910
pub mod wasm;
1011
pub use annotation::*;
1112
/// PDF standard handling

src/matrix.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ pub enum CurTransMat {
2727
}
2828

2929
impl CurTransMat {
30+
pub fn as_css_val(&self) -> String {
31+
let m = self.as_array();
32+
format!(
33+
"matrix({} {} {} {} {} {})",
34+
m[0], m[1], m[2], m[3], m[4], m[5]
35+
)
36+
}
37+
3038
pub fn combine_matrix(a: [f32; 6], b: [f32; 6]) -> [f32; 6] {
3139
let a = [
3240
[a[0], a[1], 0.0, 0.0],
@@ -249,6 +257,20 @@ pub enum TextMatrix {
249257
}
250258

251259
impl TextMatrix {
260+
pub fn as_css_val(&self, invert_y: bool) -> String {
261+
let m = self.as_array();
262+
let factor = if invert_y { -1.0 } else { 1.0 };
263+
format!(
264+
"matrix({} {} {} {} {} {})",
265+
m[0],
266+
m[1],
267+
m[2],
268+
m[3],
269+
m[4],
270+
m[5] * factor
271+
)
272+
}
273+
252274
pub fn as_array(&self) -> [f32; 6] {
253275
use self::TextMatrix::*;
254276
match self {

src/ops.rs

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use serde_derive::{Deserialize, Serialize};
22

33
use crate::{
44
BuiltinFont, DictItem, ExtendedGraphicsStateId, FontId, LayerInternalId, LinkAnnotation,
5-
PdfResources, PdfToSvgOptions, XObjectId, XObjectTransform,
5+
PdfResources, PdfToSvgOptions, RenderingIntent, XObjectId, XObjectTransform,
66
color::Color,
77
graphics::{
88
Line, LineCapStyle, LineDashPattern, LineJoinStyle, Point, Polygon, Rect, TextRenderingMode,
@@ -182,7 +182,7 @@ pub enum Op {
182182
size: Pt,
183183
cpk: Vec<(i64, u16, char)>,
184184
},
185-
/// Adds a line break to the text, depends on the line height
185+
/// `T*` Adds a line break to the text, depends on the line height
186186
AddLineBreak,
187187
/// Sets the line height for the text
188188
SetLineHeight { lh: Pt },
@@ -212,7 +212,7 @@ pub enum Op {
212212
SetTextRenderingMode { mode: TextRenderingMode },
213213
/// Sets the character spacing (default: 1.0)
214214
SetCharacterSpacing { multiplier: f32 },
215-
/// Sets the line offset (default: 1.0)
215+
/// `Ts`: Sets the line offset (default: 1.0)
216216
SetLineOffset { multiplier: f32 },
217217
/// Draw a line (colors, dashes configured earlier)
218218
DrawLine { line: Line },
@@ -232,6 +232,44 @@ pub enum Op {
232232
id: XObjectId,
233233
transform: XObjectTransform,
234234
},
235+
/// `TD` operation
236+
MoveTextCursorAndSetLeading { tx: f32, ty: f32 },
237+
/// `ri` operation
238+
SetRenderingIntent { intent: RenderingIntent },
239+
/// `Tz` operation
240+
SetHorizontalScaling { percent: f32 },
241+
/// Begins an inline image object.
242+
BeginInlineImage,
243+
/// Begins the inline image data.
244+
BeginInlineImageData,
245+
/// Ends the inline image object.
246+
EndInlineImage,
247+
/// Begins a marked content sequence.
248+
BeginMarkedContent { tag: String },
249+
/// Begins a marked content sequence with an accompanying property list.
250+
BeginMarkedContentWithProperties {
251+
tag: String,
252+
properties: Vec<DictItem>,
253+
},
254+
/// Defines a marked content point with properties.
255+
DefineMarkedContentPoint {
256+
tag: String,
257+
properties: Vec<DictItem>,
258+
},
259+
/// Ends the current marked-content sequence.
260+
EndMarkedContent,
261+
/// Begins a compatibility section (operators inside are ignored).
262+
BeginCompatibilitySection,
263+
/// Ends a compatibility section.
264+
EndCompatibilitySection,
265+
/// Moves to the next line and shows text (the `'` operator).
266+
MoveToNextLineShowText { text: String },
267+
/// Sets spacing, moves to the next line, and shows text (the `"` operator).
268+
SetSpacingMoveAndShowText {
269+
word_spacing: f32,
270+
char_spacing: f32,
271+
text: String,
272+
},
235273
/// Unknown, custom key / value operation
236274
Unknown { key: String, value: Vec<DictItem> },
237275
}

0 commit comments

Comments
 (0)