mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-16 12:22:17 +01:00
feat(chromium): support more units for paper size and margins
This commit is contained in:
@@ -2,6 +2,7 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -145,6 +146,91 @@ func (form *FormData) MandatoryDuration(key string, target *time.Duration) *Form
|
|||||||
return form.mustMandatoryField(key, target)
|
return form.mustMandatoryField(key, target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Inches binds a form field to a float64 variable. It populates an error
|
||||||
|
// if the value cannot be computed back to inches.
|
||||||
|
//
|
||||||
|
// var foo float64
|
||||||
|
//
|
||||||
|
// ctx.FormData().Inches("foo", &foo, 2.0)
|
||||||
|
func (form *FormData) Inches(key string, target *float64, defaultValue float64) *FormData {
|
||||||
|
form.inches(key, target)
|
||||||
|
if *target == -math.MaxFloat64 {
|
||||||
|
*target = defaultValue
|
||||||
|
}
|
||||||
|
return form
|
||||||
|
}
|
||||||
|
|
||||||
|
// MandatoryInches binds a form field to a float64 variable. It populates
|
||||||
|
// an error if the value cannot be computed back to inches, is empty, or the
|
||||||
|
// "key" does not exist.
|
||||||
|
//
|
||||||
|
// var foo float64
|
||||||
|
//
|
||||||
|
// ctx.FormData().MandatoryInches("foo", &foo)
|
||||||
|
func (form *FormData) MandatoryInches(key string, target *float64) *FormData {
|
||||||
|
val, ok := form.values[key]
|
||||||
|
if !ok || val[0] == "" {
|
||||||
|
form.append(
|
||||||
|
fmt.Errorf("form field '%s' is required", key),
|
||||||
|
)
|
||||||
|
return form
|
||||||
|
}
|
||||||
|
return form.inches(key, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// inches tries to compute a string value to inches.
|
||||||
|
func (form *FormData) inches(key string, target *float64) *FormData {
|
||||||
|
var value string
|
||||||
|
form.mustValue(key, &value, "")
|
||||||
|
|
||||||
|
if value == "" {
|
||||||
|
*target = -math.MaxFloat64
|
||||||
|
return form
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, unit := range []string{"pt", "px", "in", "mm", "cm", "pc"} {
|
||||||
|
if !strings.HasSuffix(value, unit) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err := strconv.ParseFloat(strings.TrimSuffix(value, unit), 64)
|
||||||
|
if err != nil {
|
||||||
|
form.append(
|
||||||
|
fmt.Errorf("form field '%s' is invalid (got '%s', resulting to %w)", key, value, err),
|
||||||
|
)
|
||||||
|
return form
|
||||||
|
}
|
||||||
|
|
||||||
|
switch unit {
|
||||||
|
case "pt":
|
||||||
|
*target = val * (1.0 / 72.0)
|
||||||
|
case "px":
|
||||||
|
*target = val * (1.0 / 96.0)
|
||||||
|
case "in":
|
||||||
|
*target = val
|
||||||
|
case "mm":
|
||||||
|
*target = val * (1.0 / 25.4)
|
||||||
|
case "cm":
|
||||||
|
*target = val * (1.0 / 2.54)
|
||||||
|
case "pc":
|
||||||
|
*target = val * (1.0 / 6.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return form
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err := strconv.ParseFloat(value, 64)
|
||||||
|
if err != nil {
|
||||||
|
form.append(
|
||||||
|
fmt.Errorf("form field '%s' is invalid (got '%s', resulting to %w)", key, value, err),
|
||||||
|
)
|
||||||
|
return form
|
||||||
|
}
|
||||||
|
|
||||||
|
*target = val
|
||||||
|
return form
|
||||||
|
}
|
||||||
|
|
||||||
// Custom helps to define a custom binding function for a form field.
|
// Custom helps to define a custom binding function for a form field.
|
||||||
//
|
//
|
||||||
// var foo map[string]string
|
// var foo map[string]string
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package api
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -768,6 +769,333 @@ func TestFormData_MandatoryDuration(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFormData_Inches(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
scenario string
|
||||||
|
form *FormData
|
||||||
|
defaultValue float64
|
||||||
|
expect float64
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
scenario: "key does not exist, fallback to default zero value",
|
||||||
|
form: &FormData{},
|
||||||
|
defaultValue: 0.0,
|
||||||
|
expect: 0.0,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "key does not exist, fallback to default value",
|
||||||
|
form: &FormData{},
|
||||||
|
defaultValue: 2.5,
|
||||||
|
expect: 2.5,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "key does exist, but empty value, fallback to default value",
|
||||||
|
form: &FormData{
|
||||||
|
values: map[string][]string{
|
||||||
|
"foo": {
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultValue: 0.0,
|
||||||
|
expect: 0.0,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "key does exist, value has a unit, but the rest is not float64 compatible",
|
||||||
|
form: &FormData{
|
||||||
|
values: map[string][]string{
|
||||||
|
"foo": {
|
||||||
|
"foomm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultValue: 0.0,
|
||||||
|
expect: 0.0,
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "key does exist, but value has no unit and is invalid",
|
||||||
|
form: &FormData{
|
||||||
|
values: map[string][]string{
|
||||||
|
"foo": {
|
||||||
|
"foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultValue: 0.0,
|
||||||
|
expect: 0.0,
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "key does exist with a pt value",
|
||||||
|
form: &FormData{
|
||||||
|
values: map[string][]string{
|
||||||
|
"foo": {
|
||||||
|
"72pt",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultValue: 0.0,
|
||||||
|
expect: 1.0,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "key does exist with a px value",
|
||||||
|
form: &FormData{
|
||||||
|
values: map[string][]string{
|
||||||
|
"foo": {
|
||||||
|
"96px",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultValue: 0.0,
|
||||||
|
expect: 1.0,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "key does exist with an in value",
|
||||||
|
form: &FormData{
|
||||||
|
values: map[string][]string{
|
||||||
|
"foo": {
|
||||||
|
"1in",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultValue: 0.0,
|
||||||
|
expect: 1.0,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "key does exist with a mm value",
|
||||||
|
form: &FormData{
|
||||||
|
values: map[string][]string{
|
||||||
|
"foo": {
|
||||||
|
"25.4mm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultValue: 0.0,
|
||||||
|
expect: 1.0,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "key does exist with a cm value",
|
||||||
|
form: &FormData{
|
||||||
|
values: map[string][]string{
|
||||||
|
"foo": {
|
||||||
|
"2.54cm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultValue: 0.0,
|
||||||
|
expect: 1.0,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "key does exist with a pc value",
|
||||||
|
form: &FormData{
|
||||||
|
values: map[string][]string{
|
||||||
|
"foo": {
|
||||||
|
"6pc",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultValue: 0.0,
|
||||||
|
expect: 1.0,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "key does exist with no unit in the value",
|
||||||
|
form: &FormData{
|
||||||
|
values: map[string][]string{
|
||||||
|
"foo": {
|
||||||
|
"100",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultValue: 0.0,
|
||||||
|
expect: 100,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
|
var actual float64
|
||||||
|
|
||||||
|
tc.form.Inches("foo", &actual, tc.defaultValue)
|
||||||
|
|
||||||
|
if fmt.Sprintf("%.1f", actual) != fmt.Sprintf("%.1f", tc.expect) {
|
||||||
|
t.Errorf("expected %.1f but got %.1f", tc.expect, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectError && tc.form.errors == nil {
|
||||||
|
t.Fatal("expected error but got none", tc.form.errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tc.expectError && tc.form.errors != nil {
|
||||||
|
t.Fatalf("expected no error but got: %v", tc.form.errors)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormData_MandatoryInches(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
scenario string
|
||||||
|
form *FormData
|
||||||
|
expect float64
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
scenario: "missing mandatory key",
|
||||||
|
form: &FormData{},
|
||||||
|
expect: 0.0,
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "mandatory value is empty",
|
||||||
|
form: &FormData{
|
||||||
|
values: map[string][]string{
|
||||||
|
"foo": {
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: 0.0,
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "mandatory value has a unit, but the rest is not float64 compatible",
|
||||||
|
form: &FormData{
|
||||||
|
values: map[string][]string{
|
||||||
|
"foo": {
|
||||||
|
"foomm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: 0.0,
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "mandatory value has no unit and is invalid",
|
||||||
|
form: &FormData{
|
||||||
|
values: map[string][]string{
|
||||||
|
"foo": {
|
||||||
|
"foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: 0.0,
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "a pt mandatory value",
|
||||||
|
form: &FormData{
|
||||||
|
values: map[string][]string{
|
||||||
|
"foo": {
|
||||||
|
"72pt",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: 1.0,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "a px mandatory value",
|
||||||
|
form: &FormData{
|
||||||
|
values: map[string][]string{
|
||||||
|
"foo": {
|
||||||
|
"96px",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: 1.0,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "an in mandatory value",
|
||||||
|
form: &FormData{
|
||||||
|
values: map[string][]string{
|
||||||
|
"foo": {
|
||||||
|
"1in",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: 1.0,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "a mm mandatory value",
|
||||||
|
form: &FormData{
|
||||||
|
values: map[string][]string{
|
||||||
|
"foo": {
|
||||||
|
"25.4mm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: 1.0,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "a cm mandatory value",
|
||||||
|
form: &FormData{
|
||||||
|
values: map[string][]string{
|
||||||
|
"foo": {
|
||||||
|
"2.54cm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: 1.0,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "a pc mandatory value",
|
||||||
|
form: &FormData{
|
||||||
|
values: map[string][]string{
|
||||||
|
"foo": {
|
||||||
|
"6pc",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: 1.0,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "no unit in the mandatory value",
|
||||||
|
form: &FormData{
|
||||||
|
values: map[string][]string{
|
||||||
|
"foo": {
|
||||||
|
"100",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: 100,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
|
var actual float64
|
||||||
|
|
||||||
|
tc.form.MandatoryInches("foo", &actual)
|
||||||
|
|
||||||
|
if fmt.Sprintf("%.1f", actual) != fmt.Sprintf("%.1f", tc.expect) {
|
||||||
|
t.Errorf("expected %.1f but got %.1f", tc.expect, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectError && tc.form.errors == nil {
|
||||||
|
t.Fatal("expected error but got none", tc.form.errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tc.expectError && tc.form.errors != nil {
|
||||||
|
t.Fatalf("expected no error but got: %v", tc.form.errors)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestFormData_Custom(t *testing.T) {
|
func TestFormData_Custom(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
scenario string
|
scenario string
|
||||||
|
|||||||
@@ -122,12 +122,12 @@ func FormDataChromiumPdfOptions(ctx *api.Context) (*api.FormData, PdfOptions) {
|
|||||||
Bool("printBackground", &printBackground, defaultPdfOptions.PrintBackground).
|
Bool("printBackground", &printBackground, defaultPdfOptions.PrintBackground).
|
||||||
Float64("scale", &scale, defaultPdfOptions.Scale).
|
Float64("scale", &scale, defaultPdfOptions.Scale).
|
||||||
Bool("singlePage", &singlePage, defaultPdfOptions.SinglePage).
|
Bool("singlePage", &singlePage, defaultPdfOptions.SinglePage).
|
||||||
Float64("paperWidth", &paperWidth, defaultPdfOptions.PaperWidth).
|
Inches("paperWidth", &paperWidth, defaultPdfOptions.PaperWidth).
|
||||||
Float64("paperHeight", &paperHeight, defaultPdfOptions.PaperHeight).
|
Inches("paperHeight", &paperHeight, defaultPdfOptions.PaperHeight).
|
||||||
Float64("marginTop", &marginTop, defaultPdfOptions.MarginTop).
|
Inches("marginTop", &marginTop, defaultPdfOptions.MarginTop).
|
||||||
Float64("marginBottom", &marginBottom, defaultPdfOptions.MarginBottom).
|
Inches("marginBottom", &marginBottom, defaultPdfOptions.MarginBottom).
|
||||||
Float64("marginLeft", &marginLeft, defaultPdfOptions.MarginLeft).
|
Inches("marginLeft", &marginLeft, defaultPdfOptions.MarginLeft).
|
||||||
Float64("marginRight", &marginRight, defaultPdfOptions.MarginRight).
|
Inches("marginRight", &marginRight, defaultPdfOptions.MarginRight).
|
||||||
String("nativePageRanges", &pageRanges, defaultPdfOptions.PageRanges).
|
String("nativePageRanges", &pageRanges, defaultPdfOptions.PageRanges).
|
||||||
Content("header.html", &headerTemplate, defaultPdfOptions.HeaderTemplate).
|
Content("header.html", &headerTemplate, defaultPdfOptions.HeaderTemplate).
|
||||||
Content("footer.html", &footerTemplate, defaultPdfOptions.FooterTemplate).
|
Content("footer.html", &footerTemplate, defaultPdfOptions.FooterTemplate).
|
||||||
|
|||||||
Reference in New Issue
Block a user