mirror of
https://github.com/unidoc/unipdf.git
synced 2025-04-26 13:48:55 +08:00
180 lines
5.0 KiB
Go
180 lines
5.0 KiB
Go
/*
|
|
* This file is subject to the terms and conditions defined in
|
|
* file 'LICENSE.md', which is part of this source code package.
|
|
*/
|
|
|
|
package model
|
|
|
|
// Common basic data structures: PdfRectangle, PdfDate, etc.
|
|
// These kinds of data structures can be copied, do not need a unique copy of each object.
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"regexp"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/unidoc/unipdf/v3/core"
|
|
)
|
|
|
|
// PdfRectangle is a definition of a rectangle.
|
|
type PdfRectangle struct {
|
|
Llx float64 // Lower left corner (ll).
|
|
Lly float64
|
|
Urx float64 // Upper right corner (ur).
|
|
Ury float64
|
|
}
|
|
|
|
// NewPdfRectangle creates a PDF rectangle object based on an input array of 4 integers.
|
|
// Defining the lower left (LL) and upper right (UR) corners with
|
|
// floating point numbers.
|
|
func NewPdfRectangle(arr core.PdfObjectArray) (*PdfRectangle, error) {
|
|
rect := PdfRectangle{}
|
|
if arr.Len() != 4 {
|
|
return nil, errors.New("invalid rectangle array, len != 4")
|
|
}
|
|
|
|
var err error
|
|
rect.Llx, err = core.GetNumberAsFloat(arr.Get(0))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rect.Lly, err = core.GetNumberAsFloat(arr.Get(1))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rect.Urx, err = core.GetNumberAsFloat(arr.Get(2))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rect.Ury, err = core.GetNumberAsFloat(arr.Get(3))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &rect, nil
|
|
}
|
|
|
|
// Height returns the height of `rect`.
|
|
func (rect *PdfRectangle) Height() float64 {
|
|
return math.Abs(rect.Ury - rect.Lly)
|
|
}
|
|
|
|
// Width returns the width of `rect`.
|
|
func (rect *PdfRectangle) Width() float64 {
|
|
return math.Abs(rect.Urx - rect.Llx)
|
|
}
|
|
|
|
// ToPdfObject converts rectangle to a PDF object.
|
|
func (rect *PdfRectangle) ToPdfObject() core.PdfObject {
|
|
return core.MakeArray(
|
|
core.MakeFloat(rect.Llx),
|
|
core.MakeFloat(rect.Lly),
|
|
core.MakeFloat(rect.Urx),
|
|
core.MakeFloat(rect.Ury),
|
|
)
|
|
}
|
|
|
|
// PdfDate represents a date, which is a PDF string of the form:
|
|
// (D:YYYYMMDDHHmmSSOHH'mm)
|
|
type PdfDate struct {
|
|
year int64 // YYYY
|
|
month int64 // MM (01-12)
|
|
day int64 // DD (01-31)
|
|
hour int64 // HH (00-23)
|
|
minute int64 // mm (00-59)
|
|
second int64 // SS (00-59)
|
|
utOffsetSign byte // O ('+' / '-' / 'Z')
|
|
utOffsetHours int64 // HH' (00-23 followed by ')
|
|
utOffsetMins int64 // mm (00-59)
|
|
}
|
|
|
|
// ToGoTime returns the date in time.Time format.
|
|
func (d PdfDate) ToGoTime() time.Time {
|
|
utcOffset := int(d.utOffsetHours*60*60 + d.utOffsetMins*60)
|
|
switch d.utOffsetSign {
|
|
case '-':
|
|
utcOffset = -utcOffset
|
|
case 'Z':
|
|
utcOffset = 0
|
|
}
|
|
tzName := fmt.Sprintf("UTC%c%.2d%.2d", d.utOffsetSign, d.utOffsetHours, d.utOffsetMins)
|
|
tz := time.FixedZone(tzName, utcOffset)
|
|
|
|
return time.Date(int(d.year), time.Month(d.month), int(d.day), int(d.hour), int(d.minute), int(d.second), 0, tz)
|
|
}
|
|
|
|
var reDate = regexp.MustCompile(`\s*D\s*:\s*(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})([+-Z])?(\d{2})?'?(\d{2})?`)
|
|
|
|
// NewPdfDate returns a new PdfDate object from a PDF date string (see 7.9.4 Dates).
|
|
// format: "D: YYYYMMDDHHmmSSOHH'mm"
|
|
func NewPdfDate(dateStr string) (PdfDate, error) {
|
|
d := PdfDate{}
|
|
|
|
matches := reDate.FindAllStringSubmatch(dateStr, 1)
|
|
if len(matches) < 1 {
|
|
return d, fmt.Errorf("invalid date string (%s)", dateStr)
|
|
}
|
|
if len(matches[0]) != 10 {
|
|
return d, errors.New("invalid regexp group match length != 10")
|
|
}
|
|
|
|
// No need to handle err from ParseInt, as pre-validated via regexp.
|
|
d.year, _ = strconv.ParseInt(matches[0][1], 10, 32)
|
|
d.month, _ = strconv.ParseInt(matches[0][2], 10, 32)
|
|
d.day, _ = strconv.ParseInt(matches[0][3], 10, 32)
|
|
d.hour, _ = strconv.ParseInt(matches[0][4], 10, 32)
|
|
d.minute, _ = strconv.ParseInt(matches[0][5], 10, 32)
|
|
d.second, _ = strconv.ParseInt(matches[0][6], 10, 32)
|
|
// Some poor implementations do not include the offset.
|
|
if len(matches[0][7]) > 0 {
|
|
d.utOffsetSign = matches[0][7][0]
|
|
} else {
|
|
d.utOffsetSign = '+'
|
|
}
|
|
if len(matches[0][8]) > 0 {
|
|
d.utOffsetHours, _ = strconv.ParseInt(matches[0][8], 10, 32)
|
|
} else {
|
|
d.utOffsetHours = 0
|
|
}
|
|
if len(matches[0][9]) > 0 {
|
|
d.utOffsetMins, _ = strconv.ParseInt(matches[0][9], 10, 32)
|
|
} else {
|
|
d.utOffsetMins = 0
|
|
}
|
|
|
|
return d, nil
|
|
}
|
|
|
|
// NewPdfDateFromTime will create a PdfDate based on the given time
|
|
func NewPdfDateFromTime(timeObj time.Time) (PdfDate, error) {
|
|
timezone := timeObj.Format("-07:00")
|
|
utOffsetHours, _ := strconv.ParseInt(timezone[1:3], 10, 32)
|
|
utOffsetMins, _ := strconv.ParseInt(timezone[4:6], 10, 32)
|
|
|
|
return PdfDate{
|
|
year: int64(timeObj.Year()),
|
|
month: int64(timeObj.Month()),
|
|
day: int64(timeObj.Day()),
|
|
hour: int64(timeObj.Hour()),
|
|
minute: int64(timeObj.Minute()),
|
|
second: int64(timeObj.Second()),
|
|
utOffsetSign: timezone[0],
|
|
utOffsetHours: utOffsetHours,
|
|
utOffsetMins: utOffsetMins,
|
|
}, nil
|
|
}
|
|
|
|
// ToPdfObject converts date to a PDF string object.
|
|
func (d *PdfDate) ToPdfObject() core.PdfObject {
|
|
str := fmt.Sprintf("D:%.4d%.2d%.2d%.2d%.2d%.2d%c%.2d'%.2d'",
|
|
d.year, d.month, d.day, d.hour, d.minute, d.second,
|
|
d.utOffsetSign, d.utOffsetHours, d.utOffsetMins)
|
|
return core.MakeString(str)
|
|
}
|