Skip to content
Merged
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
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,9 @@ sitemap.xml
Hide_Tree_Page.md
Gemfile.lock
Gemfile

# Local development workspace
.dev/
.bundle/
.jekyll-cache/
.sass-cache/
3 changes: 2 additions & 1 deletion _articles/faq/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ description: Dynamic Web TWAIN SDK Documentation FAQ
8. [How can I separate my documents automatically by barcode?](/_articles/faq/separate-documents-by-barcode.md)
9. [Is there any way to speed up the barcode reading process?](/_articles/faq/speed-up-barcode-reading-process.md)
10. [What types of webcams does the Webcam Capture addon support](/_articles/faq/webcam-supported-by-webcam-capture-addon.md)
11. [How can I prompt users for a password when loading a password-protected PDF?](/_articles/faq/prompt-password-for-protected-pdf.md)

## Project Deployment and End-user Installation

Expand Down Expand Up @@ -228,4 +229,4 @@ description: Dynamic Web TWAIN SDK Documentation FAQ

## Annotation

1. [How can I add annotations to an image and then save/upload it?](/_articles/faq/dwt-with-annotation.md)
1. [How can I add annotations to an image and then save/upload it?](/_articles/faq/dwt-with-annotation.md)
107 changes: 107 additions & 0 deletions _articles/faq/prompt-password-for-protected-pdf.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
---
layout: default-layout
noTitleIndex: true
needAutoGenerateSidebar: true
title: How can I prompt users for a password when loading a password-protected PDF?
keywords: Dynamic Web TWAIN, Addon, PDF, password, encrypted, LoadImageEx, OnPostLoad
breadcrumbText: How can I prompt users for a password when loading a password-protected PDF?
description: How can I prompt users for a password when loading a password-protected PDF?
date: 2026-04-09 10:00:00 +0800
last_modified: 2026-04-09 10:00:00 +0800
---

# Addon

## How can I prompt users for a password when loading a password-protected PDF?

Dynamic Web TWAIN does not provide a built-in password prompt UI for encrypted PDFs.
You need to handle the prompt and retry flow in your application code.

### Recommended workflow (`LoadImage()` / `LoadImageEx()`)

1. Call [`LoadImage()`](/_articles/info/api/WebTwain_IO.md#loadimage) or [`LoadImageEx()`](/_articles/info/api/WebTwain_IO.md#loadimageex).
2. In the failure callback, treat the error as "password required" only when either condition is true:
- (`errorCode` is `-1119` and `errorString` contains
`"Failed to read the PDF file because it's encrypted and the correct password is not provided."`)
- OR `errorCode` is `-1120`.
3. Prompt the user for a password.
4. Set the password via [`SetReaderOptions()`](/_articles/info/api/Addon_PDF.md#setreaderoptions).
5. Retry loading the same PDF.

### Helper function for password-required errors

Use this helper in both API-based loading and drag-and-drop handling:

```javascript
function isPasswordRequired(errorCode, errorString) {
return (
(errorCode === -1119 &&
(errorString || "").includes(
"Failed to read the PDF file because it's encrypted and the correct password is not provided.",
)) ||
errorCode === -1120
);
}
```

### `LoadImage()` / `LoadImageEx()` example

```javascript
function loadProtectedPdf(path, password) {
DWTObject.Addon.PDF.SetReaderOptions({ password: password || "" });
DWTObject.LoadImageEx(
path,
Dynamsoft.DWT.EnumDWT_ImageType.IT_PDF,
function () {
console.log("PDF loaded successfully.");
},
function (errorCode, errorString) {
if (!isPasswordRequired(errorCode, errorString)) {
console.error(errorCode, errorString);
return;
}

var userPassword = window.prompt(
"This PDF is password-protected. Please enter the password:",
"",
);
if (!userPassword) return;

loadProtectedPdf(path, userPassword);
},
);
}
```

### Drag-and-drop workflow (`OnPostLoad`)

Use [`OnPostLoad`](/_articles/info/api/WebTwain_IO.md#onpostload) and check `DWTObject.ErrorCode` / `DWTObject.ErrorString`.

```javascript
DWTObject.RegisterEvent("OnPostLoad", function (path, name, type) {
if (!isPasswordRequired(DWTObject.ErrorCode, DWTObject.ErrorString)) return;

var userPassword = window.prompt(
"This PDF is password-protected. Please enter the password:",
"",
);
if (!userPassword) return;

DWTObject.Addon.PDF.SetReaderOptions({ password: userPassword });

if (!path) {
alert("Password saved. Please drag and drop the file again.");
return;
}

var fullPath = path + "\\" + name;
loadProtectedPdf(fullPath, userPassword);
});
```

> [!NOTE]
> `-1119` can represent multiple PDF load issues, so do not use it alone.
> Starting in Dynamic Web TWAIN 19.4, encrypted-PDF load failures will use `-1120`.
>
> In `OnPostLoad`, `path` is empty for drag-and-drop files, so the code cannot directly retry with a file path.
> In this case, prompt for password, save it with `SetReaderOptions()`, and ask the user to drag and drop the file again.
2 changes: 1 addition & 1 deletion _articles/general-usage/server-side-scripting.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ description: Dynamic Web TWAIN SDK Documentation Server Scripts Page

As mentioned in our [image upload guide](/_articles/general-usage/image-export/server-upload.md), `Dynamic Web TWAIN` sends an HTTP POST request to the server when doing an upload. The file in the POST Form has the name `RemoteFile` by default. If you wish to change that, you can use [ `HttpFieldNameOfUploadedImage` ](/_articles/info/api/WebTwain_IO.md#httpfieldnameofuploadedimage).

The following assumes the default `RemoteFile` is used and that [extra Form fields](/_articles/general-usage/image-export/index.md#can-i-change-the-fields-of-the-http-form) might accompany the file.
The following assumes the default `RemoteFile` is used and that [extra Form fields](/_articles/faq/additional-form-fields.md) might accompany the file.

### Upload via CSharp

Expand Down
57 changes: 57 additions & 0 deletions scripts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Development Scripts

This folder contains local development setup scripts for this repository:

- `dev.ps1`: Windows PowerShell workflow
- `dev.sh`: Linux/WSL Bash workflow

Both scripts do the same high-level flow:

1. Prepare a local `.dev/` workspace in this repo.
2. Clone or sync `Docs-Template-Repo` into `.dev/Docs-Template-Repo`.
3. Build a merged workspace in `.dev/DocHome` (docs + template).
4. Replace `assetsPath` values in `_includes` and `_layouts`.
5. Install gems with Bundler using local cache directories under `.dev/`.
6. Run `jekyll serve` (or print the serve command when no-serve mode is used).

## Windows (`dev.ps1`)

Run from repo root:

```powershell
powershell -ExecutionPolicy Bypass -File .\scripts\dev.ps1
```

Useful options:

```powershell
powershell -ExecutionPolicy Bypass -File .\scripts\dev.ps1 -NoServe
powershell -ExecutionPolicy Bypass -File .\scripts\dev.ps1 -NoTemplateUpdate
powershell -ExecutionPolicy Bypass -File .\scripts\dev.ps1 -Port 6001
powershell -ExecutionPolicy Bypass -File .\scripts\dev.ps1 -BindHost 0.0.0.0
```

## Linux / WSL (`dev.sh`)

Run from repo root:

```bash
bash ./scripts/dev.sh
```

Useful options:

```bash
bash ./scripts/dev.sh --no-serve
bash ./scripts/dev.sh --no-template-update
bash ./scripts/dev.sh --port 6001
bash ./scripts/dev.sh --bind-host 0.0.0.0
```

## Output

Default local URL root after start:

```text
http://localhost:5555/web-twain/docs/
```
156 changes: 156 additions & 0 deletions scripts/dev.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
[CmdletBinding()]
param(
[string]$TemplateRepoUrl = "https://github.com/dynamsoft-docs/Docs-Template-Repo.git",
[string]$TemplateBranch = "preview",
[int]$Port = 5555,
[string]$BindHost = "localhost",
[switch]$NoTemplateUpdate,
[switch]$NoServe
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

function Write-Step {
param([string]$Message)
Write-Host "==> $Message"
}

function Ensure-Command {
param([string]$Name)
if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) {
throw "Required command '$Name' was not found in PATH."
}
}

function Invoke-Native {
param(
[Parameter(Mandatory = $true)]
[string]$FilePath,
[string[]]$Arguments = @()
)

& $FilePath @Arguments
$code = $LASTEXITCODE
if ($code -ne 0) {
$argText = ($Arguments -join " ")
throw "Command failed with exit code ${code}: $FilePath $argText"
}
}

function Invoke-Robocopy {
param(
[string]$Source,
[string]$Destination,
[string[]]$ExtraArgs
)

$baseArgs = @(
$Source,
$Destination,
"/R:2",
"/W:2",
"/NFL",
"/NDL",
"/NJH",
"/NJS",
"/NP"
)
$allArgs = $baseArgs + $ExtraArgs
& robocopy @allArgs | Out-Null
$code = $LASTEXITCODE
if ($code -ge 8) {
throw "robocopy failed with exit code $code. Source: $Source Destination: $Destination"
}
}

Ensure-Command "git"
Ensure-Command "robocopy"
Ensure-Command "bundle"

$repoRoot = Split-Path -Parent $PSScriptRoot
$devRoot = Join-Path $repoRoot ".dev"
$templateRoot = Join-Path $devRoot "Docs-Template-Repo"
$docHome = Join-Path $devRoot "DocHome"
$bundlePath = Join-Path $devRoot "vendor\bundle"
$bundleConfig = Join-Path $devRoot ".bundle"
$bundleUserHome = Join-Path $devRoot ".bundle-user"
$jekyllCache = Join-Path $devRoot ".jekyll-cache"
$siteDir = Join-Path $devRoot "_site"

Write-Step "Preparing local workspace in $devRoot"
New-Item -ItemType Directory -Force -Path $devRoot | Out-Null

if (-not (Test-Path $templateRoot)) {
Write-Step "Cloning template repo ($TemplateBranch)"
Invoke-Native -FilePath "git" -Arguments @("clone", "--depth", "1", "--branch", $TemplateBranch, $TemplateRepoUrl, $templateRoot)
} elseif (-not $NoTemplateUpdate) {
Write-Step "Syncing template repo ($TemplateBranch)"
Invoke-Native -FilePath "git" -Arguments @("-C", $templateRoot, "fetch", "origin", $TemplateBranch, "--depth", "1")
Invoke-Native -FilePath "git" -Arguments @("-C", $templateRoot, "reset", "--hard")
Invoke-Native -FilePath "git" -Arguments @("-C", $templateRoot, "clean", "-fd")
Invoke-Native -FilePath "git" -Arguments @("-C", $templateRoot, "checkout", "-B", $TemplateBranch, "origin/$TemplateBranch")
} else {
Write-Step "Skipping template update"
}

Write-Step "Rebuilding merged site workspace"
New-Item -ItemType Directory -Force -Path $docHome | Out-Null
Invoke-Robocopy -Source $repoRoot -Destination $docHome -ExtraArgs @(
"/MIR",
"/XD", ".git", ".dev", ".vs", "node_modules", "_site", ".bundle", ".jekyll-cache", ".sass-cache", "vendor"
)
Invoke-Robocopy -Source $templateRoot -Destination $docHome -ExtraArgs @(
"/E",
"/XD", ".git", ".github", "_site", "node_modules"
)

Write-Step "Applying local assetsPath replacements"
$search = "assetsPath = '/webres/wwwroot'"
$replace = "assetsPath = 'https://www.dynamsoft.com/webres/wwwroot'"
foreach ($dirName in @("_includes", "_layouts")) {
$dirPath = Join-Path $docHome $dirName
if (-not (Test-Path $dirPath)) {
continue
}

Get-ChildItem -Path $dirPath -Recurse -File | ForEach-Object {
$path = $_.FullName
try {
$content = [System.IO.File]::ReadAllText($path)
} catch {
return
}
if ($content.Contains($search)) {
$updated = $content.Replace($search, $replace)
[System.IO.File]::WriteAllText($path, $updated)
}
}
}

Write-Step "Configuring Bundler and Jekyll local paths"
$env:BUNDLE_PATH = $bundlePath
$env:BUNDLE_APP_CONFIG = $bundleConfig
$env:BUNDLE_USER_HOME = $bundleUserHome
$env:BUNDLE_USER_CACHE = Join-Path $bundleUserHome "cache"
$env:JEKYLL_CACHE_DIR = $jekyllCache
$env:JEKYLL_ENV = "development"
New-Item -ItemType Directory -Force -Path $bundlePath, $bundleConfig, $bundleUserHome, $jekyllCache, $siteDir | Out-Null

Push-Location $docHome
try {
Write-Step "Installing Ruby dependencies"
Invoke-Native -FilePath "bundle" -Arguments @("install")

if ($NoServe) {
Write-Step "Build workspace prepared. Start server with:"
Write-Host "bundle exec jekyll serve -P $Port --trace --host=$BindHost --livereload --destination `"$siteDir`""
} else {
Write-Step "Starting Jekyll server on port $Port"
Invoke-Native -FilePath "bundle" -Arguments @("exec", "jekyll", "serve", "-P", "$Port", "--trace", "--host=$BindHost", "--livereload", "--destination", "$siteDir")
}
} finally {
Pop-Location
}


Loading
Loading