Skip to content

Commit

Permalink
Add zoom cmd, fix #756
Browse files Browse the repository at this point in the history
  • Loading branch information
hhrutter committed Mar 3, 2024
1 parent d581dc1 commit c0a39e9
Show file tree
Hide file tree
Showing 13 changed files with 782 additions and 4 deletions.
1 change: 1 addition & 0 deletions cmd/pdfcpu/init.go
Expand Up @@ -297,6 +297,7 @@ func initCommandMap() {
"watermark": {nil, watermarkCmdMap, usageWatermark, usageLongWatermark},
"version": {printVersion, nil, usageVersion, usageLongVersion},
"viewerpref": {nil, viewerPrefsCmdMap, usageViewerPreferences, usageLongViewerPreferences},
"zoom": {processZoomCommand, nil, usageZoom, usageLongZoom},
} {
cmdMap.register(k, v)
}
Expand Down
34 changes: 34 additions & 0 deletions cmd/pdfcpu/process.go
Expand Up @@ -2576,3 +2576,37 @@ func processResetViewerPreferencesCommand(conf *model.Configuration) {
}
process(cli.ResetViewerPreferencesCommand(inFile, "", conf))
}

func processZoomCommand(conf *model.Configuration) {
if len(flag.Args()) < 2 || len(flag.Args()) > 3 {
fmt.Fprintf(os.Stderr, "%s\n", usageZoom)
os.Exit(1)
}

processDiplayUnit(conf)

zc, err := pdfcpu.ParseZoomConfig(flag.Arg(0), conf.Unit)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}

inFile := flag.Arg(1)
if conf.CheckFileNameExt {
ensurePDFExtension(inFile)
}

outFile := ""
if len(flag.Args()) == 3 {
outFile = flag.Arg(2)
ensurePDFExtension(outFile)
}

selectedPages, err := api.ParsePageSelection(selectedPages)
if err != nil {
fmt.Fprintf(os.Stderr, "problem with flag selectedPages: %v\n", err)
os.Exit(1)
}

process(cli.ZoomCommand(inFile, outFile, selectedPages, zc, conf))
}
29 changes: 28 additions & 1 deletion cmd/pdfcpu/usage.go
Expand Up @@ -57,7 +57,7 @@ The commands are:
paper print list of supported paper sizes
permissions list, set user access permissions
portfolio list, add, remove, extract portfolio entries with optional description
poster cut selected pages into poster using paper size or dimensions
poster cut selected pages into poster by paper size or dimensions
properties list, add, remove document properties
resize scale selected pages
rotate rotate selected pages
Expand All @@ -69,6 +69,7 @@ The commands are:
version print version
viewerpref list, set, reset viewer preferences for opened document
watermark add, remove, update Unicode text, image or PDF watermarks for selected pages
zoom zoom in/out of selected pages by magnification factor or corresponding margin
All instantly recognizable command prefixes are supported eg. val for validation
One letter Unix style abbreviations supported for flags and command parameters.
Expand Down Expand Up @@ -1601,4 +1602,30 @@ description ... scalefactor, dimensions, formsize, enforce, border, bgcolor
}
`

usageZoom = "usage: pdfcpu zoom [-p(ages) selectedPages] -- description inFile [outFile]" + generalFlags

usageLongZoom = `Zoom in/out of selected pages either by magnification factor or corresponding margin.
pages ... Please refer to "pdfcpu selectedpages"
description ... factor, hmargin, vmargin, border, bgcolor
inFile ... input PDF file
outFile ... output PDF file
Examples:
pdfcpu zoom -- "factor: 2" in.pdf out.pdf ... zoom in to magnification of 200%
pdfcpu zoom -- "factor: .5" in.pdf out.pdf ... zoom out to magnification of 50%
pdfcpu zoom -- "hmargin: -10" in.pdf out.pdf ... zoom in to horizontal margin of -10 points
pdfcpu zoom -- "hmargin: 10" in.pdf out.pdf ... zoom out to horizontal margin of 10 points
pdfcpu zoom -unit cm -- "hmargin: -1" in.pdf out.pdf ... zoom in to horizontal margin of -1 cm
pdfcpu zoom -unit cm -- "hmargin: 1" in.pdf out.pdf ... zoom out to horizontal margin of 1 cm
pdfcpu zoom -- "vmargin: -10" in.pdf out.pdf ... zoom in to vertical margin of -10 points
pdfcpu zoom -- "vmargin: 10" in.pdf out.pdf ... zoom out to vertical margin of 10 points
pdfcpu zoom -unit cm -- "vmargin: -1" in.pdf out.pdf ... zoom in to vertical margin of -1 cm
pdfcpu zoom -unit cm -- "vmargin: 1, border:true, bgcolor:lightgray" in.pdf out.pdf ... zoom out to vertical margin of 1 cm
`
)
2 changes: 1 addition & 1 deletion pkg/api/resize.go
Expand Up @@ -26,7 +26,7 @@ import (
"github.com/pkg/errors"
)

// ResizeFile applies resizeConf for selected pages of rs and writes result to w.
// Resize applies resizeConf for selected pages of rs and writes result to w.
func Resize(rs io.ReadSeeker, w io.Writer, selectedPages []string, resize *model.Resize, conf *model.Configuration) error {
if rs == nil {
return errors.New("pdfcpu: Resize: missing rs")
Expand Down
122 changes: 122 additions & 0 deletions pkg/api/test/zoom_test.go
@@ -0,0 +1,122 @@
/*
Copyright 2024 The pdf Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package test

import (
"path/filepath"
"testing"

"github.com/pdfcpu/pdfcpu/pkg/api"
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu"
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/types"
)

func TestZoomInByFactor(t *testing.T) {
msg := "TestZoomInByFactor"

inFile := filepath.Join(inDir, "test.pdf")

zoom, err := pdfcpu.ParseZoomConfig("factor:2", types.POINTS)
if err != nil {
t.Fatalf("%s invalid zoom configuration: %v\n", msg, err)
}
outFile := filepath.Join(samplesDir, "zoom", "zoomInByFactor2.pdf")
if err := api.ZoomFile(inFile, outFile, nil, zoom, nil); err != nil {
t.Fatalf("%s zoom: %v\n", msg, err)
}

zoom, err = pdfcpu.ParseZoomConfig("factor:4", types.POINTS)
if err != nil {
t.Fatalf("%s invalid zoom configuration: %v\n", msg, err)
}
outFile = filepath.Join(samplesDir, "zoom", "zoomInByFactor4.pdf")
if err := api.ZoomFile(inFile, outFile, nil, zoom, nil); err != nil {
t.Fatalf("%s zoom: %v\n", msg, err)
}
}

func TestZoomOutByFactor(t *testing.T) {
msg := "TestZoomOutByFactor"

inFile := filepath.Join(inDir, "test.pdf")

zoom, err := pdfcpu.ParseZoomConfig("factor:.5", types.POINTS)
if err != nil {
t.Fatalf("%s invalid zoom configuration: %v\n", msg, err)
}
outFile := filepath.Join(samplesDir, "zoom", "zoomOutByFactor05.pdf")
if err := api.ZoomFile(inFile, outFile, nil, zoom, nil); err != nil {
t.Fatalf("%s zoom: %v\n", msg, err)
}

zoom, err = pdfcpu.ParseZoomConfig("factor:.25, border:true", types.POINTS)
if err != nil {
t.Fatalf("%s invalid zoom configuration: %v\n", msg, err)
}
outFile = filepath.Join(samplesDir, "zoom", "zoomOutByFactor025.pdf")
if err := api.ZoomFile(inFile, outFile, nil, zoom, nil); err != nil {
t.Fatalf("%s zoom: %v\n", msg, err)
}
}

func TestZoomOutByHorizontalMargin(t *testing.T) {
// Zoom out of page content resulting in a preferred horizontal margin.
msg := "TestZoomOutByHMargin"
inFile := filepath.Join(inDir, "test.pdf")

zoom, err := pdfcpu.ParseZoomConfig("hmargin:149", types.POINTS)
if err != nil {
t.Fatalf("%s invalid zoom configuration: %v\n", msg, err)
}
outFile := filepath.Join(samplesDir, "zoom", "zoomOutByHMarginPoints.pdf")
if err := api.ZoomFile(inFile, outFile, nil, zoom, nil); err != nil {
t.Fatalf("%s zoom: %v\n", msg, err)
}

zoom, err = pdfcpu.ParseZoomConfig("hmargin:1, border:true, bgcol:lightgray", types.CENTIMETRES)
if err != nil {
t.Fatalf("%s invalid zoom configuration: %v\n", msg, err)
}
outFile = filepath.Join(samplesDir, "zoom", "zoomOutByHMarginCm.pdf")
if err := api.ZoomFile(inFile, outFile, nil, zoom, nil); err != nil {
t.Fatalf("%s zoom: %v\n", msg, err)
}
}

func TestZoomOutByVerticalMargin(t *testing.T) {
// Zoom out of page content resulting in a preferred vertical margin.
msg := "TestZoomOutByVMargin"
inFile := filepath.Join(inDir, "test.pdf")

zoom, err := pdfcpu.ParseZoomConfig("vmargin:1", types.INCHES)
if err != nil {
t.Fatalf("%s invalid zoom configuration: %v\n", msg, err)
}
outFile := filepath.Join(samplesDir, "zoom", "zoomOutByVMarginInches.pdf")
if err := api.ZoomFile(inFile, outFile, nil, zoom, nil); err != nil {
t.Fatalf("%s zoom: %v\n", msg, err)
}

zoom, err = pdfcpu.ParseZoomConfig("vmargin:30, border:false, bgcol:lightgray", types.MILLIMETRES)
if err != nil {
t.Fatalf("%s invalid zoom configuration: %v\n", msg, err)
}
outFile = filepath.Join(samplesDir, "zoom", "zoomOutByVMarginMm.pdf")
if err := api.ZoomFile(inFile, outFile, nil, zoom, nil); err != nil {
t.Fatalf("%s zoom: %v\n", msg, err)
}
}
108 changes: 108 additions & 0 deletions pkg/api/zoom.go
@@ -0,0 +1,108 @@
/*
Copyright 2024 The pdfcpu Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package api

import (
"io"
"os"

"github.com/pdfcpu/pdfcpu/pkg/log"
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu"
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model"
"github.com/pkg/errors"
)

// Zoom applies resizeConf for selected pages of rs and writes result to w.
func Zoom(rs io.ReadSeeker, w io.Writer, selectedPages []string, zoom *model.Zoom, conf *model.Configuration) error {
if rs == nil {
return errors.New("pdfcpu: Zoom: missing rs")
}

if conf == nil {
conf = model.NewDefaultConfiguration()
}
conf.Cmd = model.ZOOM

ctx, err := ReadValidateAndOptimize(rs, conf)
if err != nil {
return err
}

pages, err := PagesForPageSelection(ctx.PageCount, selectedPages, true, true)
if err != nil {
return err
}

if err = pdfcpu.Zoom(ctx, pages, zoom); err != nil {
return err
}

return Write(ctx, w, conf)
}

// ZoomFile applies zoomConf for selected pages of inFile and writes result to outFile.
func ZoomFile(inFile, outFile string, selectedPages []string, zoom *model.Zoom, conf *model.Configuration) (err error) {
if log.CLIEnabled() {
log.CLI.Printf("zooming %s\n", inFile)
}

tmpFile := inFile + ".tmp"
if outFile != "" && inFile != outFile {
tmpFile = outFile
logWritingTo(outFile)
} else {
logWritingTo(inFile)
}

var (
f1, f2 *os.File
)

if f1, err = os.Open(inFile); err != nil {
return err
}

if f2, err = os.Create(tmpFile); err != nil {
f1.Close()
return err
}

defer func() {
if err != nil {
f2.Close()
f1.Close()
os.Remove(tmpFile)
return
}
if err = f2.Close(); err != nil {
return
}
if err = f1.Close(); err != nil {
return
}
if outFile == "" || inFile == outFile {
err = os.Rename(tmpFile, inFile)
}
}()

if conf == nil {
conf = model.NewDefaultConfiguration()
}
conf.Cmd = model.ZOOM

return Zoom(f1, f2, selectedPages, zoom, conf)
}
6 changes: 5 additions & 1 deletion pkg/cli/cli.go
Expand Up @@ -410,11 +410,15 @@ func SetViewerPreferences(cmd *Command) ([]string, error) {
if *cmd.InFileJSON != "" {
return nil, api.SetViewerPreferencesFileFromJSONFile(*cmd.InFile, *cmd.OutFile, *cmd.InFileJSON, cmd.Conf)
}

return nil, api.SetViewerPreferencesFileFromJSONBytes(*cmd.InFile, *cmd.OutFile, []byte(cmd.StringVal), cmd.Conf)
}

// ResetViewerPreferences resets inFile's viewer preferences.
func ResetViewerPreferences(cmd *Command) ([]string, error) {
return nil, api.ResetViewerPreferencesFile(*cmd.InFile, *cmd.OutFile, cmd.Conf)
}

// Zoom in/out of selected pages either by zoom factor or corresponding margin.
func Zoom(cmd *Command) ([]string, error) {
return nil, api.ZoomFile(*cmd.InFile, *cmd.OutFile, cmd.PageSelection, cmd.Zoom, cmd.Conf)
}
17 changes: 17 additions & 0 deletions pkg/cli/cmd.go
Expand Up @@ -52,6 +52,7 @@ type Command struct {
Cut *model.Cut
PageBoundaries *model.PageBoundaries
Resize *model.Resize
Zoom *model.Zoom
Watermark *model.Watermark
ViewerPreferences *model.ViewerPreferences
Conf *model.Configuration
Expand Down Expand Up @@ -136,6 +137,7 @@ var cmdMap = map[model.CommandMode]func(cmd *Command) ([]string, error){
model.LISTVIEWERPREFERENCES: processViewerPreferences,
model.SETVIEWERPREFERENCES: processViewerPreferences,
model.RESETVIEWERPREFERENCES: processViewerPreferences,
model.ZOOM: Zoom,
}

// ValidateCommand creates a new command to validate a file.
Expand Down Expand Up @@ -1209,3 +1211,18 @@ func ResetViewerPreferencesCommand(inFile, outFile string, conf *model.Configura
OutFile: &outFile,
Conf: conf}
}

// ZoomCommand creates a new command to zoom in/out of selected pages.
func ZoomCommand(inFile, outFile string, pageSelection []string, zoom *model.Zoom, conf *model.Configuration) *Command {
if conf == nil {
conf = model.NewDefaultConfiguration()
}
conf.Cmd = model.ZOOM
return &Command{
Mode: model.ZOOM,
InFile: &inFile,
OutFile: &outFile,
PageSelection: pageSelection,
Zoom: zoom,
Conf: conf}
}

0 comments on commit c0a39e9

Please sign in to comment.