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
58 changes: 58 additions & 0 deletions minipdf-java/minipdf-cli/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.github.minisoftware</groupId>
<artifactId>minipdf-java-parent</artifactId>
<version>0.1.0-SNAPSHOT</version>
</parent>

<artifactId>minipdf-cli</artifactId>
<name>MiniPdf Java CLI</name>

<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>minipdf</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>info.picocli</groupId>
<artifactId>picocli</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>io.github.minisoftware.minipdf.cli.MiniPdfCommand</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package io.github.minisoftware.minipdf.cli;

import io.github.minisoftware.minipdf.ConversionOptions;
import io.github.minisoftware.minipdf.MiniPdf;
import io.github.minisoftware.minipdf.MiniPdfException;
import io.github.minisoftware.minipdf.PageSize;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import picocli.CommandLine.Spec;
import picocli.CommandLine.Model.CommandSpec;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Locale;
import java.util.concurrent.Callable;

@Command(
name = "minipdf",
version = "minipdf-java 0.1.0-SNAPSHOT",
description = "Convert XLSX and DOCX files to PDF with the Java MiniPdf engine.",
mixinStandardHelpOptions = true,
subcommands = MiniPdfCommand.ConvertCommand.class)
public final class MiniPdfCommand implements Callable<Integer> {
@Spec
private CommandSpec spec;

@Parameters(index = "0", arity = "0..1", paramLabel = "INPUT")
private Path input;

@Mixin
private ConversionArguments arguments = new ConversionArguments();

public static void main(String[] args) {
CommandLine commandLine = createCommandLine();
int exitCode = commandLine.execute(args);
System.exit(exitCode);
}

public static CommandLine createCommandLine() {
CommandLine commandLine = new CommandLine(new MiniPdfCommand());
commandLine.setCaseInsensitiveEnumValuesAllowed(true);
commandLine.setExecutionExceptionHandler((exception, current, parseResult) -> {
current.getErr().println("Error: " + exception.getMessage());
return 1;
});
commandLine.setParameterExceptionHandler((exception, args) -> {
exception.getCommandLine().getErr().println("Error: " + exception.getMessage());
return 1;
});
return commandLine;
}

@Override
public Integer call() throws Exception {
if (input == null) {
throw new CommandLine.ParameterException(spec.commandLine(), "input file is required");
}
return convert(input, arguments, spec.commandLine());
}

@Command(name = "convert", description = "Convert an Office document to PDF.", mixinStandardHelpOptions = true)
static final class ConvertCommand implements Callable<Integer> {
@Spec
private CommandSpec spec;

@Parameters(index = "0", paramLabel = "INPUT")
private Path input;

@Mixin
private ConversionArguments arguments = new ConversionArguments();

@Override
public Integer call() throws Exception {
return convert(input, arguments, spec.commandLine());
}
}

static final class ConversionArguments {
@Option(names = {"-o", "--output"}, paramLabel = "OUTPUT")
private Path output;

@Option(names = "--fonts", paramLabel = "DIR")
private Path fonts;

@Option(names = "--paper-size", paramLabel = "SIZE")
private PaperSizeArgument paperSize;

@Option(names = "--page-width", paramLabel = "POINTS")
private Float pageWidth;

@Option(names = "--page-height", paramLabel = "POINTS")
private Float pageHeight;

private ConversionOptions conversionOptions(CommandLine commandLine) throws MiniPdfException {
if (paperSize != null && (pageWidth != null || pageHeight != null)) {
throw new CommandLine.ParameterException(
commandLine,
"use either --paper-size or --page-width/--page-height, not both");
}
if ((pageWidth == null) != (pageHeight == null)) {
throw new CommandLine.ParameterException(
commandLine,
"--page-width and --page-height must be specified together");
}
if (paperSize != null) {
return ConversionOptions.withPageSize(paperSize == PaperSizeArgument.A4
? PageSize.A4
: PageSize.LETTER);
}
if (pageWidth != null) {
return ConversionOptions.withPageSize(PageSize.of(pageWidth, pageHeight));
}
return ConversionOptions.defaults();
}
}

enum PaperSizeArgument {
A4,
LETTER
}

private static int convert(Path input, ConversionArguments arguments, CommandLine commandLine)
throws MiniPdfException, IOException {
if (!Files.isRegularFile(input)) {
throw new CommandLine.ParameterException(commandLine, "file not found: " + input);
}
String fileName = input.getFileName().toString();
int dot = fileName.lastIndexOf('.');
String extension = dot < 0 ? "" : fileName.substring(dot + 1).toLowerCase(Locale.ROOT);
if (!extension.equals("xlsx") && !extension.equals("docx")) {
throw new CommandLine.ParameterException(
commandLine,
"unsupported file type '." + extension + "'. Supported: .xlsx, .docx");
}

if (arguments.fonts != null) {
registerFonts(arguments.fonts, commandLine);
}
Path output = arguments.output == null ? replaceExtension(input, "pdf") : arguments.output;
MiniPdf.convertToPdf(input, output, arguments.conversionOptions(commandLine));
commandLine.getOut().println(output);
return 0;
}

private static void registerFonts(Path directory, CommandLine commandLine) throws IOException {
if (!Files.isDirectory(directory)) {
throw new CommandLine.ParameterException(commandLine, "font directory not found: " + directory);
}
try (var paths = Files.list(directory)) {
for (Path path : paths.filter(Files::isRegularFile).toList()) {
String name = path.getFileName().toString();
int dot = name.lastIndexOf('.');
String extension = dot < 0 ? "" : name.substring(dot + 1).toLowerCase(Locale.ROOT);
if (extension.equals("ttf") || extension.equals("ttc") || extension.equals("otf")) {
MiniPdf.registerFont(dot < 0 ? name : name.substring(0, dot), Files.readAllBytes(path));
}
}
}
}

private static Path replaceExtension(Path input, String extension) {
String fileName = input.getFileName().toString();
int dot = fileName.lastIndexOf('.');
String outputName = (dot < 0 ? fileName : fileName.substring(0, dot)) + '.' + extension;
return input.resolveSibling(outputName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package io.github.minisoftware.minipdf.cli;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import picocli.CommandLine;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

class MiniPdfCommandTest {
private static final Path REPOSITORY_ROOT = Path.of("..", "..").toAbsolutePath().normalize();

@TempDir
Path temporaryDirectory;

@Test
void convertsXlsxWithDirectSyntax() throws Exception {
Path source = REPOSITORY_ROOT.resolve(
"tests/MiniPdf.Scripts/output/classic01_basic_table_with_headers.xlsx");
Path output = temporaryDirectory.resolve("direct.pdf");

CommandResult result = execute(source.toString(), "-o", output.toString());

assertEquals(0, result.exitCode());
assertTrue(Files.readString(output, StandardCharsets.ISO_8859_1).startsWith("%PDF-1.4"));
assertTrue(result.stdout().contains(output.toString()));
}

@Test
void convertsDocxWithSubcommandAndCustomSize() throws Exception {
Path source = REPOSITORY_ROOT.resolve(
"tests/MiniPdf.Scripts/output_docx/docx_classic01_single_paragraph.docx");
Path output = temporaryDirectory.resolve("subcommand.pdf");

CommandResult result = execute(
"convert", source.toString(), "-o", output.toString(),
"--page-width", "400", "--page-height", "500");

assertEquals(0, result.exitCode());
assertTrue(Files.readString(output, StandardCharsets.ISO_8859_1)
.contains("/MediaBox [0 0 400 500]"));
}

@Test
void rejectsConflictingPageOptions() {
Path source = REPOSITORY_ROOT.resolve(
"tests/MiniPdf.Scripts/output/classic01_basic_table_with_headers.xlsx");

CommandResult result = execute(
source.toString(), "--paper-size", "a4", "--page-width", "400", "--page-height", "500");

assertEquals(1, result.exitCode());
assertTrue(result.stderr().contains("use either --paper-size"));
}

private static CommandResult execute(String... arguments) {
CommandLine commandLine = MiniPdfCommand.createCommandLine();
StringWriter stdout = new StringWriter();
StringWriter stderr = new StringWriter();
commandLine.setOut(new PrintWriter(stdout, true));
commandLine.setErr(new PrintWriter(stderr, true));
int exitCode = commandLine.execute(arguments);
return new CommandResult(exitCode, stdout.toString(), stderr.toString());
}

private record CommandResult(int exitCode, String stdout, String stderr) {
}
}
23 changes: 23 additions & 0 deletions minipdf-java/minipdf/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.github.minisoftware</groupId>
<artifactId>minipdf-java-parent</artifactId>
<version>0.1.0-SNAPSHOT</version>
</parent>

<artifactId>minipdf</artifactId>
<name>MiniPdf Java Library</name>

<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.github.minisoftware.minipdf;

import java.util.Optional;

public final class ConversionOptions {
private static final ConversionOptions DEFAULTS = new ConversionOptions(null);

private final PageSize pageSize;

private ConversionOptions(PageSize pageSize) {
this.pageSize = pageSize;
}

public static ConversionOptions defaults() {
return DEFAULTS;
}

public static ConversionOptions withPageSize(PageSize pageSize) {
return new ConversionOptions(java.util.Objects.requireNonNull(pageSize, "pageSize"));
}

public Optional<PageSize> pageSize() {
return Optional.ofNullable(pageSize);
}
}
Loading
Loading