mirror of
https://github.com/unidoc/unipdf.git
synced 2025-04-27 13:48:51 +08:00
commit
112c39a889
89
README.md
89
README.md
@ -1,53 +1,50 @@
|
||||
# UniPDF - PDF for Go
|
||||
|
||||
[UniDoc](http://unidoc.io)'s UniPDF is a powerful PDF library for Go (golang) with capabilities for
|
||||
creating and processing PDF files. The library is written and supported by
|
||||
the [FoxyUtils.com](https://foxyutils.com) website, where the library is used to power
|
||||
many of the PDF services offered.
|
||||
[UniDoc](http://unidoc.io)'s UniPDF (formerly unidoc) is a PDF library for Go (golang) with capabilities for
|
||||
creating and reading, processing PDF files. The library is written and supported by
|
||||
[FoxyUtils.com](https://foxyutils.com), where the library is used to power many of its services.
|
||||
|
||||
[](https://app.wercker.com/project/bykey/22b50db125a6d376080f3f0c80d085fa)
|
||||
[](https://github.com/unidoc/unipdf/releases)
|
||||
[](https://www.gnu.org/licenses/agpl-3.0)
|
||||
[](https://goreportcard.com/report/github.com/unidoc/unipdf)
|
||||
[](https://godoc.org/github.com/unidoc/unipdf)
|
||||
|
||||
## News
|
||||
- unidoc is being renamed to unipdf and will be maintained under https://github.com/unidoc/unipdf
|
||||
- The old repository will remain under https://github.com/unidoc/unidoc for backwards compatibility and will be read-only.
|
||||
All development will be under the unipdf repository.
|
||||
- The initial release of unipdf v3.0.0 will be compliant with Go modules from the start.
|
||||
|
||||
|
||||
## Features
|
||||
unipdf has a powerful set of features both for reading, processing and writing PDF.
|
||||
The following list describes some key features:
|
||||
|
||||
- [x] [Create PDF reports](https://github.com/unidoc/unipdf-examples/blob/v3/report/pdf_report.go)
|
||||
- [x] [Create PDF invoices](https://unidoc.io/news/simple-invoices)
|
||||
- [x] Advanced table generation in the creator with subtable support
|
||||
- [x] Paragraph in creator handling multiple styles within the same paragraph
|
||||
- [x] Table of contents automatically generated
|
||||
- [x] Text extraction significantly improved in quality and foundation in place for vectorized (position-based) text extraction (XY)
|
||||
- [x] Image extraction with coordinates
|
||||
- [x] [Merge PDF pages](https://github.com/unidoc/unipdf-examples/blob/v3/pages/pdf_merge.go)
|
||||
- [x] Merge page contents
|
||||
- [x] [Split PDF pages and change page order](https://github.com/unidoc/unipdf-examples/blob/v3/pages/pdf_split.go)
|
||||
- [x] [Rotate pages](https://github.com/unidoc/unipdf-examples/blob/v3/pages/pdf_rotate.go)
|
||||
- [x] [Extract text from PDF files](https://github.com/unidoc/unipdf-examples/blob/v3/text/pdf_extract_text.go)
|
||||
- [x] Extract images
|
||||
- [x] Add images to pages
|
||||
- [x] [Compress and optimize PDF output](https://github.com/unidoc/unipdf-examples/blob/v3/compress/pdf_optimize.g)
|
||||
- [x] [Draw watermark on PDF files](https://github.com/unidoc/unipdf-examples/blob/v3/image/pdf_watermark_image.go)
|
||||
- [x] Advanced page manipulation (blocks/templates)
|
||||
- [x] Load PDF templates and modify
|
||||
- [x] [Flatten forms and generate appearance streams](https://github.com/unidoc/unipdf-examples/blob/v3/forms/pdf_form_flatten.go)
|
||||
- [x] [Fill out forms and FDF merging](https://github.com/unidoc/unipdf-examples/tree/v3/forms)
|
||||
- [x] [FDF merge](https://github.com/unidoc/unipdf-examples/blob/v3/forms/pdf_form_fill_fdf_merge.go) and [form filling via JSON data](https://github.com/unidoc/unipdf-examples/blob/v3/forms/pdf_form_fill_json.go)
|
||||
- [x] [Form creation](https://github.com/unidoc/unipdf-examples/blob/v3/forms/pdf_form_add.go)
|
||||
- [x] [Unlock PDF files / remove password](https://github.com/unidoc/unipdf-examples/blob/v3/security/pdf_unlock.go)
|
||||
- [x] [Protect PDF files with a password](https://github.com/unidoc/unipdf-examples/blob/v3/security/pdf_protect.go)
|
||||
- [x] [Digital signing validation and signing](https://github.com/unidoc/unipdf-examples/tree/v3/signatures)
|
||||
- [x] CCITTFaxDecode decoding and encoding support
|
||||
- [x] Append mode
|
||||
- [Create PDF reports](https://github.com/unidoc/unipdf-examples/blob/v3/report/pdf_report.go)
|
||||
- [Invoice creation](https://unidoc.io/news/simple-invoices)
|
||||
- Advanced table generation in the creator with subtable support
|
||||
- Paragraph in creator handling multiple styles within the same paragraph
|
||||
- [Merge PDF pages](https://github.com/unidoc/unipdf-examples/blob/v3/pages/pdf_merge.go)
|
||||
- [Split PDF pages](https://github.com/unidoc/unipdf-examples/blob/v3/pages/pdf_split.go) and change page order
|
||||
- [Rotate pages](https://github.com/unidoc/unipdf-examples/blob/v3/pages/pdf_rotate.go)
|
||||
- [Extract text from PDF files](https://github.com/unidoc/unipdf-examples/blob/v3/text/pdf_extract_text.go)
|
||||
- [Extract images](https://github.com/unidoc/unipdf-examples/blob/v3/image/pdf_extract_images.go) with coordinates
|
||||
- [Images to PDF](https://github.com/unidoc/unipdf-examples/blob/v3/image/pdf_images_to_pdf.go)
|
||||
- [Add images to pages](https://github.com/unidoc/unipdf-examples/blob/v3/image/pdf_add_image_to_page.go)
|
||||
- [Compress and optimize PDF](https://github.com/unidoc/unipdf-examples/blob/v3/compress/pdf_optimize.go)
|
||||
- [Watermark PDF files](https://github.com/unidoc/unipdf-examples/blob/v3/image/pdf_watermark_image.go)
|
||||
- Advanced page manipulation (blocks/templates)
|
||||
- Load PDF templates and modify
|
||||
- [Form creation](https://github.com/unidoc/unipdf-examples/blob/v3/forms/pdf_form_add.go)
|
||||
- [Fill and flatten forms](https://github.com/unidoc/unipdf-examples/blob/v3/forms/pdf_form_flatten.go)
|
||||
- [Fill out forms](https://github.com/unidoc/unipdf-examples/blob/v3/forms/pdf_form_fill_json.go) and [FDF merging](https://github.com/unidoc/unipdf-examples/blob/v3/forms/pdf_form_fill_fdf_merge.go)
|
||||
- [Unlock PDF files / remove password](https://github.com/unidoc/unipdf-examples/blob/v3/security/pdf_unlock.go)
|
||||
- [Protect PDF files with a password](https://github.com/unidoc/unipdf-examples/blob/v3/security/pdf_protect.go)
|
||||
- [Digital signing validation and signing](https://github.com/unidoc/unipdf-examples/tree/v3/signatures)
|
||||
- CCITTFaxDecode decoding and encoding support
|
||||
|
||||
Multiple examples are provided in our example repository https://github.com/unidoc/unidoc-examples
|
||||
as well as [documented examples](https://unidoc.io/examples) on our website.
|
||||
|
||||
Contact us if you need any specific examples.
|
||||
|
||||
## News
|
||||
- unidoc has been renamed to unipdf and is maintained under https://github.com/unidoc/unipdf
|
||||
- The old repository remains under https://github.com/unidoc/unidoc for backwards compatibility and will be read-only.
|
||||
All development is under the unipdf repository.
|
||||
- The initial release of unipdf v3.0.0 is compatible with Go modules from the start.
|
||||
|
||||
## Installation
|
||||
With modules:
|
||||
@ -55,6 +52,11 @@ With modules:
|
||||
go get github.com/unidoc/unipdf/v3
|
||||
~~~
|
||||
|
||||
With GOPATH:
|
||||
~~~
|
||||
go get github.com/unidoc/unipdf/...
|
||||
~~~
|
||||
|
||||
|
||||
## How can I convince myself and my boss to buy unipdf rather using a free alternative?
|
||||
|
||||
@ -67,13 +69,6 @@ Security. We take security very seriously and we restrict access to github.com/
|
||||
The profits are invested back into making unipdf better. We want to make the best possible product and in order to do that we need the best people to contribute. A large fraction of the profits made goes back into developing unipdf. That way we have been able to get many excellent people to work and contribute to unipdf that would not be able to contribute their work for free.
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
Multiple examples are provided in our example repository https://github.com/unidoc/unidoc-examples
|
||||
as well as [documented examples](https://unidoc.io/examples) on our website.
|
||||
|
||||
Contact us if you need any specific examples.
|
||||
|
||||
## Contributing
|
||||
|
||||
[](https://cla-assistant.io/unidoc/unipdf)
|
||||
|
@ -11,12 +11,12 @@ import (
|
||||
)
|
||||
|
||||
const releaseYear = 2019
|
||||
const releaseMonth = 4
|
||||
const releaseDay = 20
|
||||
const releaseHour = 23
|
||||
const releaseMonth = 6
|
||||
const releaseDay = 2
|
||||
const releaseHour = 11
|
||||
const releaseMin = 30
|
||||
|
||||
// Version holds version information, when bumping this make sure to bump the released at stamp also.
|
||||
const Version = "3.0.0-rc.1"
|
||||
const Version = "3.0.1"
|
||||
|
||||
var ReleasedAt = time.Date(releaseYear, releaseMonth, releaseDay, releaseHour, releaseMin, 0, 0, time.UTC)
|
||||
|
@ -605,13 +605,16 @@ func (array *PdfObjectArray) GetAsFloat64Slice() ([]float64, error) {
|
||||
}
|
||||
|
||||
// Merge merges in key/values from another dictionary. Overwriting if has same keys.
|
||||
func (d *PdfObjectDictionary) Merge(another *PdfObjectDictionary) {
|
||||
// The mutated dictionary (d) is returned in order to allow method chaining.
|
||||
func (d *PdfObjectDictionary) Merge(another *PdfObjectDictionary) *PdfObjectDictionary {
|
||||
if another != nil {
|
||||
for _, key := range another.Keys() {
|
||||
val := another.Get(key)
|
||||
d.Set(key, val)
|
||||
}
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
// String returns a string describing `d`.
|
||||
|
@ -226,6 +226,49 @@ func (p *StyledParagraph) Height() float64 {
|
||||
return height
|
||||
}
|
||||
|
||||
// getLineHeight returns both the capheight and the font size based height of
|
||||
// the line with the specified index.
|
||||
func (p *StyledParagraph) getLineHeight(idx int) (capHeight, height float64) {
|
||||
if p.lines == nil || len(p.lines) == 0 {
|
||||
p.wrapText()
|
||||
}
|
||||
if idx < 0 || idx > len(p.lines)-1 {
|
||||
common.Log.Debug("ERROR: invalid paragraph line index %d. Returning 0, 0", idx)
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
line := p.lines[idx]
|
||||
for _, chunk := range line {
|
||||
descriptor, err := chunk.Style.Font.GetFontDescriptor()
|
||||
if err != nil {
|
||||
common.Log.Debug("ERROR: Unable to get font descriptor")
|
||||
}
|
||||
|
||||
var fontCapHeight float64
|
||||
if descriptor != nil {
|
||||
if fontCapHeight, err = descriptor.GetCapHeight(); err != nil {
|
||||
common.Log.Debug("ERROR: Unable to get font CapHeight: %v", err)
|
||||
}
|
||||
}
|
||||
if int(fontCapHeight) <= 0 {
|
||||
common.Log.Debug("WARN: CapHeight not available - setting to 1000")
|
||||
fontCapHeight = 1000
|
||||
}
|
||||
|
||||
h := fontCapHeight / 1000.0 * chunk.Style.FontSize * p.lineHeight
|
||||
if h > capHeight {
|
||||
capHeight = h
|
||||
}
|
||||
|
||||
h = p.lineHeight * chunk.Style.FontSize
|
||||
if h > height {
|
||||
height = h
|
||||
}
|
||||
}
|
||||
|
||||
return capHeight, height
|
||||
}
|
||||
|
||||
// getTextWidth calculates the text width as if all in one line (not taking
|
||||
// wrapping into account).
|
||||
func (p *StyledParagraph) getTextWidth() float64 {
|
||||
|
@ -798,3 +798,66 @@ func TestStyledLinkRotation(t *testing.T) {
|
||||
t.Fatalf("Fail: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStyledParagraphTableVerticalAlignment(t *testing.T) {
|
||||
c := New()
|
||||
|
||||
fontRegular := newStandard14Font(t, model.CourierName)
|
||||
|
||||
createTable := func(c *Creator, text string, align CellVerticalAlignment, fontSize float64) {
|
||||
textStyle := c.NewTextStyle()
|
||||
textStyle.Font = fontRegular
|
||||
textStyle.FontSize = fontSize
|
||||
|
||||
table := c.NewTable(1)
|
||||
table.SetMargins(0, 0, 5, 5)
|
||||
|
||||
cell := table.NewCell()
|
||||
sp := c.NewStyledParagraph()
|
||||
textChunk := sp.Append(text)
|
||||
textChunk.Style = textStyle
|
||||
|
||||
cell.SetVerticalAlignment(align)
|
||||
cell.SetContent(sp)
|
||||
cell.SetBorder(CellBorderSideAll, CellBorderStyleSingle, 1)
|
||||
|
||||
if err := c.Draw(table); err != nil {
|
||||
t.Fatalf("Error drawing: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
chunks := []string{
|
||||
"TR",
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Lacus viverra vitae congue eu consequat. Cras adipiscing enim eu turpis. Lectus magna fringilla urna porttitor. Condimentum id venenatis a condimentum. Quis ipsum suspendisse ultrices gravida dictum fusce. In fermentum posuere urna nec tincidunt.",
|
||||
}
|
||||
|
||||
alignments := []struct {
|
||||
Label string
|
||||
Alignment CellVerticalAlignment
|
||||
}{
|
||||
{"Top alignment", CellVerticalAlignmentTop},
|
||||
{"Middle alignment", CellVerticalAlignmentMiddle},
|
||||
{"Bottom alignment", CellVerticalAlignmentBottom},
|
||||
}
|
||||
|
||||
for _, chunk := range chunks {
|
||||
for _, alignment := range alignments {
|
||||
c.NewPage()
|
||||
|
||||
sp := c.NewStyledParagraph()
|
||||
sp.Append(alignment.Label).Style.FontSize = 16
|
||||
sp.SetMargins(0, 0, 0, 5)
|
||||
|
||||
if err := c.Draw(sp); err != nil {
|
||||
t.Fatalf("Error drawing: %v", err)
|
||||
}
|
||||
|
||||
for i := 4; i <= 20; i += 2 {
|
||||
createTable(c, chunk, alignment.Alignment, float64(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write output file.
|
||||
testWriteAndRender(t, c, "styled_paragraph_table_vertical_align.pdf")
|
||||
}
|
||||
|
@ -516,8 +516,10 @@ func (table *Table) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext,
|
||||
}
|
||||
|
||||
if cell.content != nil {
|
||||
// content width.
|
||||
cw := cell.content.Width()
|
||||
cw := cell.content.Width() // content width.
|
||||
ch := cell.content.Height() // content height.
|
||||
vertOffset := 0.0
|
||||
|
||||
switch t := cell.content.(type) {
|
||||
case *Paragraph:
|
||||
if t.enableWrap {
|
||||
@ -527,6 +529,26 @@ func (table *Table) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext,
|
||||
if t.enableWrap {
|
||||
cw = t.getMaxLineWidth() / 1000.0
|
||||
}
|
||||
|
||||
// Calculate the height of the paragraph.
|
||||
lineCapHeight, lineHeight := t.getLineHeight(0)
|
||||
if len(t.lines) == 1 {
|
||||
ch = lineCapHeight
|
||||
} else {
|
||||
ch = ch - lineHeight + lineCapHeight
|
||||
}
|
||||
|
||||
// Account for the top offset the paragraph adds.
|
||||
vertOffset = lineCapHeight - t.defaultStyle.FontSize*t.lineHeight
|
||||
|
||||
switch cell.verticalAlignment {
|
||||
case CellVerticalAlignmentTop:
|
||||
// Add a bit of space from the top border of the cell.
|
||||
vertOffset += lineCapHeight * 0.5
|
||||
case CellVerticalAlignmentBottom:
|
||||
// Add a bit of space from the bottom border of the cell.
|
||||
vertOffset -= lineCapHeight * 0.5
|
||||
}
|
||||
case *Table:
|
||||
cw = w
|
||||
case *List:
|
||||
@ -553,8 +575,9 @@ func (table *Table) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext,
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Y += vertOffset
|
||||
|
||||
// Account for vertical alignment.
|
||||
ch := cell.content.Height() // content height.
|
||||
switch cell.verticalAlignment {
|
||||
case CellVerticalAlignmentTop:
|
||||
// Default: do nothing.
|
||||
@ -567,7 +590,7 @@ func (table *Table) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext,
|
||||
case CellVerticalAlignmentBottom:
|
||||
if h > ch {
|
||||
ctx.Y = ctx.Y + h - ch
|
||||
ctx.Height = ch
|
||||
ctx.Height = h
|
||||
}
|
||||
}
|
||||
|
||||
@ -575,6 +598,8 @@ func (table *Table) GeneratePageBlocks(ctx DrawContext) ([]*Block, DrawContext,
|
||||
if err != nil {
|
||||
common.Log.Debug("ERROR: %v", err)
|
||||
}
|
||||
|
||||
ctx.Y -= vertOffset
|
||||
}
|
||||
|
||||
ctx.Y += h
|
||||
|
@ -30,8 +30,8 @@ var (
|
||||
// knownHashes defines a list of known output hashes to ensure that the output is constant.
|
||||
// If there is a change in hash need to find out why and update only if the change is accepted.
|
||||
var knownHashes = map[string]string{
|
||||
"bf7c9d5dabc7e7ec2fc0cf9db2d9c8e7aa456fca.pdf": "f7891d491fa9f20ed2975dd28961c205",
|
||||
"371dce2c2720581a3eef3f123e5741dd3566ef87.pdf": "4a25934226b6b64e5d95d571260b1f01",
|
||||
"bf7c9d5dabc7e7ec2fc0cf9db2d9c8e7aa456fca.pdf": "fdd638603c6f655babbc90358de66107",
|
||||
"371dce2c2720581a3eef3f123e5741dd3566ef87.pdf": "4c5356ac623a96004d80315f24613fff",
|
||||
"e815311526b50036db6e89c54af2b9626edecf30.pdf": "97dcfdde59a2f3a6eb105d0c31ebd3fb",
|
||||
"3bf64014e0c9e4a56f1a9363f1b34fd707bd9fa0.pdf": "6f310c9fdd44d49766d3cc32d3053b89",
|
||||
"004feecd47e2da4f2ed5cdbbf4791a77dd59ce20.pdf": "309a072a97d0566aa3f85edae504bb53",
|
||||
|
@ -66,6 +66,8 @@ func TestAppenderNoop(t *testing.T) {
|
||||
appender, err := model.NewPdfAppender(reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
model.SetPdfProducer("UniPDF")
|
||||
model.SetPdfCreator("UniDoc UniPDF")
|
||||
err = appender.WriteToFile(tempFile("appender_noop.pdf"))
|
||||
if err != nil {
|
||||
t.Errorf("Fail: %v\n", err)
|
||||
@ -123,7 +125,7 @@ func TestAppenderNoop(t *testing.T) {
|
||||
3: core.XrefObject{ObjectNumber: 3, XType: 0, Offset: 178},
|
||||
4: core.XrefObject{ObjectNumber: 4, XType: 0, Offset: 457},
|
||||
5: core.XrefObject{ObjectNumber: 5, XType: 0, Offset: 740},
|
||||
6: core.XrefObject{ObjectNumber: 6, XType: 0, Offset: 860},
|
||||
6: core.XrefObject{ObjectNumber: 6, XType: 0, Offset: 802},
|
||||
}
|
||||
require.Equal(t, expected, xrefs.ObjectMap)
|
||||
}
|
||||
|
@ -532,11 +532,11 @@ func (w *PdfWriter) addObjects(obj core.PdfObject) error {
|
||||
|
||||
// AddPage adds a page to the PDF file. The new page should be an indirect object.
|
||||
func (w *PdfWriter) AddPage(page *PdfPage) error {
|
||||
procPage(page)
|
||||
obj := page.ToPdfObject()
|
||||
|
||||
common.Log.Trace("==========")
|
||||
common.Log.Trace("Appending to page list %T", obj)
|
||||
procPage(page)
|
||||
|
||||
pageObj, ok := obj.(*core.PdfIndirectObject)
|
||||
if !ok {
|
||||
@ -627,14 +627,16 @@ func procPage(p *PdfPage) {
|
||||
return
|
||||
}
|
||||
|
||||
// Add font as needed.
|
||||
f := DefaultFont()
|
||||
p.Resources.SetFontByName("UF1", f.ToPdfObject())
|
||||
// Add font, if needed.
|
||||
fontName := core.PdfObjectName("UF1")
|
||||
if !p.Resources.HasFontByName(fontName) {
|
||||
p.Resources.SetFontByName(fontName, DefaultFont().ToPdfObject())
|
||||
}
|
||||
|
||||
var ops []string
|
||||
ops = append(ops, "q")
|
||||
ops = append(ops, "BT")
|
||||
ops = append(ops, "/UF1 14 Tf")
|
||||
ops = append(ops, fmt.Sprintf("/%s 14 Tf", fontName.String()))
|
||||
ops = append(ops, "1 0 0 rg")
|
||||
ops = append(ops, "10 10 Td")
|
||||
s := "Unlicensed UniDoc - Get a license on https://unidoc.io"
|
||||
|
Loading…
x
Reference in New Issue
Block a user