diff --git a/.gitignore b/.gitignore index d284ccdf..9d98baa5 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,9 @@ sitemap.xml Hide_Tree_Page.md Gemfile.lock Gemfile + +# Local development workspace +.dev/ +.bundle/ +.jekyll-cache/ +.sass-cache/ diff --git a/_articles/faq/index.md b/_articles/faq/index.md index 97584b20..2eca36a0 100644 --- a/_articles/faq/index.md +++ b/_articles/faq/index.md @@ -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 @@ -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) \ No newline at end of file +1. [How can I add annotations to an image and then save/upload it?](/_articles/faq/dwt-with-annotation.md) diff --git a/_articles/faq/prompt-password-for-protected-pdf.md b/_articles/faq/prompt-password-for-protected-pdf.md new file mode 100644 index 00000000..d163d195 --- /dev/null +++ b/_articles/faq/prompt-password-for-protected-pdf.md @@ -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. diff --git a/_articles/general-usage/server-side-scripting.md b/_articles/general-usage/server-side-scripting.md index 7452c9f1..d3d41f20 100644 --- a/_articles/general-usage/server-side-scripting.md +++ b/_articles/general-usage/server-side-scripting.md @@ -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 diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 00000000..870522d7 --- /dev/null +++ b/scripts/README.md @@ -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/ +``` diff --git a/scripts/dev.ps1 b/scripts/dev.ps1 new file mode 100644 index 00000000..1df8c47a --- /dev/null +++ b/scripts/dev.ps1 @@ -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 +} + + diff --git a/scripts/dev.sh b/scripts/dev.sh new file mode 100755 index 00000000..ed540028 --- /dev/null +++ b/scripts/dev.sh @@ -0,0 +1,176 @@ +#!/usr/bin/env bash + +set -euo pipefail + +template_repo_url="https://github.com/dynamsoft-docs/Docs-Template-Repo.git" +template_branch="preview" +port="5555" +bind_host="localhost" +no_template_update="false" +no_serve="false" + +write_step() { + echo "==> $1" +} + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "Required command '$1' was not found in PATH." >&2 + exit 1 + fi +} + +show_help() { + cat <<'EOF' +Usage: ./scripts/dev.sh [options] + +Options: + --template-repo-url URL Template repository URL + --template-branch NAME Template branch (default: preview) + --port N Jekyll port (default: 5555) + --bind-host HOST Jekyll bind host (default: localhost) + --no-template-update Skip template fetch/pull + --no-serve Prepare workspace only (do not start Jekyll) + -h, --help Show this help message +EOF +} + +require_option_value() { + if [[ $# -lt 2 || -z "${2:-}" || "${2:-}" == --* ]]; then + echo "Missing value for option: $1" >&2 + show_help + exit 1 + fi +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --template-repo-url) + require_option_value "$@" + template_repo_url="$2" + shift 2 + ;; + --template-branch) + require_option_value "$@" + template_branch="$2" + shift 2 + ;; + --port) + require_option_value "$@" + port="$2" + shift 2 + ;; + --bind-host) + require_option_value "$@" + bind_host="$2" + shift 2 + ;; + --no-template-update) + no_template_update="true" + shift + ;; + --no-serve) + no_serve="true" + shift + ;; + -h|--help) + show_help + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + show_help + exit 1 + ;; + esac +done + +require_cmd git +require_cmd rsync +require_cmd bundle +require_cmd find +require_cmd sed + +script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd -- "$script_dir/.." && pwd)" +dev_root="$repo_root/.dev" +template_root="$dev_root/Docs-Template-Repo" +doc_home="$dev_root/DocHome" +bundle_path="$dev_root/vendor/bundle" +bundle_config="$dev_root/.bundle" +bundle_user_home="$dev_root/.bundle-user" +jekyll_cache="$dev_root/.jekyll-cache" +site_dir="$dev_root/_site" + +write_step "Preparing local workspace in $dev_root" +mkdir -p "$dev_root" + +if [[ ! -d "$template_root/.git" ]]; then + write_step "Cloning template repo ($template_branch)" + git clone --depth 1 --branch "$template_branch" "$template_repo_url" "$template_root" +elif [[ "$no_template_update" != "true" ]]; then + write_step "Syncing template repo ($template_branch)" + git -C "$template_root" fetch origin "$template_branch" --depth 1 + git -C "$template_root" reset --hard + git -C "$template_root" clean -fd + git -C "$template_root" checkout -B "$template_branch" "origin/$template_branch" +else + write_step "Skipping template update" +fi + +write_step "Rebuilding merged site workspace" +rm -rf "$doc_home" +mkdir -p "$doc_home" +rsync -a --delete \ + --exclude '.git' \ + --exclude '.dev' \ + --exclude '.vs' \ + --exclude 'node_modules' \ + --exclude '_site' \ + --exclude '.bundle' \ + --exclude '.jekyll-cache' \ + --exclude '.sass-cache' \ + --exclude 'vendor' \ + "$repo_root"/ "$doc_home"/ +rsync -a \ + --exclude '.git' \ + --exclude '.github' \ + --exclude '_site' \ + --exclude 'node_modules' \ + "$template_root"/ "$doc_home"/ + +write_step "Applying local assetsPath replacements" +search="assetsPath = '/webres/wwwroot'" +replace="assetsPath = 'https://www.dynamsoft.com/webres/wwwroot'" +for dir_name in "_includes" "_layouts"; do + dir_path="$doc_home/$dir_name" + if [[ ! -d "$dir_path" ]]; then + continue + fi + + while IFS= read -r -d '' file_path; do + sed -i "s|$search|$replace|g" "$file_path" + done < <(find "$dir_path" -type f -print0) +done + +write_step "Configuring Bundler and Jekyll local paths" +export BUNDLE_PATH="$bundle_path" +export BUNDLE_APP_CONFIG="$bundle_config" +export BUNDLE_USER_HOME="$bundle_user_home" +export BUNDLE_USER_CACHE="$bundle_user_home/cache" +export JEKYLL_CACHE_DIR="$jekyll_cache" +export JEKYLL_ENV="development" +mkdir -p "$bundle_path" "$bundle_config" "$bundle_user_home" "$jekyll_cache" "$site_dir" + +pushd "$doc_home" >/dev/null +write_step "Installing Ruby dependencies" +bundle install + +if [[ "$no_serve" == "true" ]]; then + write_step "Build workspace prepared. Start server with:" + echo "bundle exec jekyll serve -P $port --trace --host=$bind_host --livereload --destination \"$site_dir\"" +else + write_step "Starting Jekyll server on port $port" + bundle exec jekyll serve -P "$port" --trace --host="$bind_host" --livereload --destination "$site_dir" +fi +popd >/dev/null