Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
236 changes: 236 additions & 0 deletions lib/nutrient_dws/processor/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,220 @@ def redact(file:, text: [])
make_request(instructions, files: { 'document' => file })
end

def duplicate_pages(file:, page: nil, start_page: nil, end_page: nil)
parts = []

if page
# Duplicate a single page
parts << build_part_with_pages(file, 'document', page, page)
elsif start_page && end_page
# Duplicate a page range
parts << build_part_with_pages(file, 'document', start_page, end_page)
else
# Duplicate the entire document (no page specification)
parts << build_part(file, 'document')
end

# Add the original document as well to create the duplication effect
parts << build_part(file, 'document')

instructions = {
parts: parts
}

make_request(instructions, files: { 'document' => file })
end

def delete_pages(file:, page: nil, start_page: nil, end_page: nil, keep_before: nil, keep_after: nil)
parts = []

if page
# Delete a single page - keep pages before and after
if page > 0
parts << build_part_with_pages(file, 'document', 0, page - 1)
end
parts << build_part_with_pages(file, 'document', page + 1, -1)
elsif start_page && end_page
# Delete a page range - keep pages before and after the range
if start_page > 0
parts << build_part_with_pages(file, 'document', 0, start_page - 1)
end
parts << build_part_with_pages(file, 'document', end_page + 1, -1)
elsif keep_before
# Keep pages before a certain point (delete from that point onwards)
parts << build_part_with_pages(file, 'document', 0, keep_before - 1)
elsif keep_after
# Keep pages after a certain point (delete from beginning to that point)
parts << build_part_with_pages(file, 'document', keep_after + 1, -1)
else
raise ArgumentError, 'Must specify pages to delete or pages to keep'
end

# Remove empty parts
parts = parts.reject { |part| part.dig(:pages, :start) == part.dig(:pages, :end) && part.dig(:pages, :start) == -1 }

instructions = {
parts: parts
}

make_request(instructions, files: { 'document' => file })
end

def flatten(file:)
instructions = {
parts: [
build_part(file, 'document')
],
actions: [
{
type: 'flatten'
}
]
}

make_request(instructions, files: { 'document' => file })
end

def rotate(file:, rotate_by:)
unless [90, 180, 270].include?(rotate_by)
raise ArgumentError, "Invalid rotation angle: #{rotate_by}. Supported angles: 90, 180, 270"
end

instructions = {
parts: [
build_part(file, 'document')
],
actions: [
{
type: 'rotate',
rotateBy: rotate_by
}
]
}

make_request(instructions, files: { 'document' => file })
end

def add_page(file:, position: :end, after_page: nil, page_count: 1, page_size: 'Letter')
parts = []

if position == :beginning
# Add new page(s) at the beginning
parts << build_new_page_part(page_count, page_size)
parts << build_part(file, 'document')
elsif position == :end
# Add new page(s) at the end
parts << build_part(file, 'document')
parts << build_new_page_part(page_count, page_size)
elsif after_page
# Add new page(s) after a specific page
parts << build_part_with_pages(file, 'document', 0, after_page)
parts << build_new_page_part(page_count, page_size)
parts << build_part_with_pages(file, 'document', after_page + 1, -1)
else
raise ArgumentError, 'Must specify position (:beginning, :end) or after_page'
end

instructions = {
parts: parts
}

make_request(instructions, files: { 'document' => file })
end

def set_page_label(file:, labels:)
# Convert labels to the API format
formatted_labels = labels.map do |label|
if label[:page]
{
pages: { start: label[:page], end: label[:page] },
label: label[:label]
}
elsif label[:start_page] && label[:end_page]
{
pages: { start: label[:start_page], end: label[:end_page] },
label: label[:label]
}
else
raise ArgumentError, 'Each label must specify either :page or :start_page and :end_page'
end
end

instructions = {
parts: [
build_part(file, 'document')
],
output: {
type: 'pdf',
labels: formatted_labels
}
}

make_request(instructions, files: { 'document' => file })
end

def json_import(file:, json_data: nil, json_file: nil)
raise ArgumentError, 'Either json_data or json_file must be provided' if json_data.nil? && json_file.nil?

files = { 'document' => file }

if json_data
# Create a temporary file for the JSON data
require 'tempfile'
temp_file = Tempfile.new(['annotations', '.json'])

# Handle both Hash and String inputs
if json_data.is_a?(Hash)
temp_file.write(JSON.dump(json_data))
else
temp_file.write(json_data.to_s)
end

temp_file.close
files['annotations.json'] = temp_file.path
elsif json_file
files['annotations.json'] = json_file
end

instructions = {
parts: [
build_part(file, 'document')
],
actions: [
{
type: 'applyInstantJson',
file: 'annotations.json'
}
]
}

result = make_request(instructions, files: files)

# Clean up temporary file if created
if json_data && temp_file
temp_file.close
temp_file.unlink
end

result
end

def xfdf_import(file:, xfdf_file:)
instructions = {
parts: [
build_part(file, 'document')
],
actions: [
{
type: 'applyXfdf',
file: 'xfdf_data'
}
]
}

make_request(instructions, files: { 'document' => file, 'xfdf_data' => xfdf_file })
end

private

def parse_page_ranges(ranges)
Expand All @@ -201,6 +415,28 @@ def build_part(file, file_key)
end
end

def build_part_with_pages(file, file_key, start_page, end_page)
part = build_part(file, file_key)

if url?(file)
part[:file][:pages] = { start: start_page, end: end_page }
else
part[:pages] = { start: start_page, end: end_page }
end

part
end

def build_new_page_part(page_count, page_size)
{
page: 'new',
pageCount: page_count,
layout: {
size: page_size
}
}
end

def url?(file)
file.to_s.match?(%r{\Ahttps?://})
end
Expand Down
144 changes: 144 additions & 0 deletions spec/fixtures/annotations.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
{
"annotations": [
{
"backgroundColor": "#2293FB",
"bbox": [50, 50, 150, 150],
"borderWidth": 0,
"createdAt": "1970-01-01T00:00:00Z",
"font": "Helvetica",
"fontColor": "#FFFFFF",
"fontSize": 18,
"fontStyle": ["bold"],
"horizontalAlign": "center",
"id": "01HZ5BHHGEF82KD85CBWZ2CVY6",
"isFitting": true,
"lineHeightFactor": 1.190000057220459,
"name": "01HZ5BHHGEF82KD85CBWZ2CVY6",
"opacity": 1,
"pageIndex": 0,
"rotation": 0,
"text": {
"format": "plain",
"value": "Welcome to\nPSPDFKit"
},
"type": "pspdfkit/text",
"updatedAt": "1970-01-01T00:00:00Z",
"v": 2,
"verticalAlign": "center"
},
{
"bbox": [50, 50, 150, 50],
"createdAt": "1970-01-01T00:00:00Z",
"id": "01HZ5BHHGEJEXECJJ9H4R70WJV",
"isDrawnNaturally": false,
"lineWidth": 5,
"lines": {
"intensities": [
[0.5, 0.5],
[0.5, 0.5],
[0.5, 0.5]
],
"points": [
[
[50, 50],
[200, 50]
],
[
[50, 75],
[200, 75]
],
[
[50, 100],
[200, 100]
]
]
},
"name": "01HZ5BHHGEJEXECJJ9H4R70WJV",
"opacity": 1,
"pageIndex": 1,
"strokeColor": "#FFFFFF",
"type": "pspdfkit/ink",
"updatedAt": "1970-01-01T00:00:00Z",
"v": 2
},
{
"bbox": [390, 380, 120, 120],
"createdAt": "1970-01-01T00:00:00Z",
"id": "01HZ5BHHGEVA0GBYDSXEV6DBHA",
"name": "01HZ5BHHGEVA0GBYDSXEV6DBHA",
"opacity": 1,
"pageIndex": 0,
"strokeColor": "#2293FB",
"strokeWidth": 5,
"type": "pspdfkit/shape/ellipse",
"updatedAt": "1970-01-01T00:00:00Z",
"v": 2
},
{
"bbox": [500, 20, 30, 30],
"color": "#FFD83F",
"createdAt": "1970-01-01T00:00:00Z",
"icon": "comment",
"id": "01HZ5BHHGFGP0TV6CSPQ16BX5M",
"name": "01HZ5BHHGFGP0TV6CSPQ16BX5M",
"opacity": 1,
"pageIndex": 0,
"text": {
"format": "plain",
"value": "An example for a Note Annotation"
},
"type": "pspdfkit/note",
"updatedAt": "1970-01-01T00:00:00Z",
"v": 2
},
{
"bbox": [30, 424, 223, 83],
"blendMode": "multiply",
"color": "#FCEE7C",
"createdAt": "1970-01-01T00:00:00Z",
"id": "01HZ5BHHGFTYARCZGKGZXW2AYZ",
"name": "01HZ5BHHGFTYARCZGKGZXW2AYZ",
"opacity": 1,
"pageIndex": 0,
"rects": [
[30, 424, 223, 42],
[30, 465, 122, 42]
],
"type": "pspdfkit/markup/highlight",
"updatedAt": "1970-01-01T00:00:00Z",
"v": 2
},
{
"action": {
"type": "uri",
"uri": "http://www.ribrand.si/en/set-of-utensils.html"
},
"bbox": [28.346500396728516, 695, 100.84251403808594, 20.4920654296875],
"borderStyle": "solid",
"borderWidth": 1,
"createdAt": "2024-05-21T08:36:34Z",
"creatorName": "Button 1",
"formFieldName": "Button 1",
"id": "7QHT8EFNNHD1DAEG3Y6A65F8C5",
"opacity": 1,
"pageIndex": 0,
"rotation": 0,
"type": "pspdfkit/widget",
"updatedAt": "2024-05-21T08:36:34Z",
"v": 2
}
],
"formFields": [
{
"annotationIds": ["7QHT8EFNNHD1DAEG3Y6A65F8C5"],
"buttonLabel": "",
"id": "7QHT8EFNQ7JRNJYJ3N2RD56WJJ",
"label": "Button 1",
"name": "Button 1",
"pdfObjectId": 94,
"type": "pspdfkit/form-field/button",
"v": 1
}
],
"format": "https://pspdfkit.com/instant-json/v1"
}
Loading
Loading