Release v2.1.0

This commit is contained in:
Santiago Lezica 2021-03-17 15:28:04 -03:00
parent ff5c3ffdf1
commit 0255762ab4
63 changed files with 4699 additions and 708 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
.idea
neutrino_test
/bin

View File

@ -1,36 +1,45 @@
![muun](https://muun.com/images/github-banner-v2.png)
## About
Welcome!
You can use this tool to swipe all the funds in your muun account to an address of your choosing.
You can use this tool to transfer all funds from your Muun wallet to an address of your choosing.
To do this you will need:
* The recovery code, that you set up when you created your muun account
* The two encrypted private keys that you exported from your muun wallet
* A destination Bitcoin address where all your funds will be sent
**This process requires no collaboration from Muun to work**. We wholeheartedly believe that self-custodianship
is an essential right, and we want to create a world in which people have complete and exclusive
control over their own money. Bitcoin has finally made this possible.
The process of scanning the blockchain to recover your funds can take several hours, please be ready to keep it running. The scan starts at the block your wallet was created to make it faster, but depending on when that was it can take long.
## Usage
## Setup
To execute a recovery, you will need:
1. **Your Recovery Code**, which you wrote down during your security setup
2. **Your Emergency Kit PDF**, which you exported from the app
3. **Your destination bitcoin address**, where all your funds will be sent
Once you have that, you must:
1. Install [golang](https://golang.org/)
2. Open a terminal window
3. Run this code:
3. Run:
```
git clone https://github.com/muun/recovery
cd recovery
go run -mod=vendor .
```
git clone https://github.com/muun/recovery
cd recovery
./recovery-tool <path to your Emergency Kit PDF>
The recovery process takes only a few minutes (depending on your connection).
## Questions
If you have any questions, contact us at contact@muun.com
If you have any questions, we'll be happy to answer them. Contact us at [contact@muun.com](mailto:contact@muun.com)
## Auditing
* Most of the key handling and transaction crafting operations happens in the **libwallet** module.
* All the blockchain scan code is in the **neutrino** module.
Begin by reading `main.go`, and follow calls to other files and modules as you see fit. We always work
to improve code quality and readability with each release, so that auditing is easier and more effective.
The low-level encryption, key handling and transaction crafting code can be found in the `libwallet`
module, and it's the same our iOS and Android applications use.
## Responsible Disclosure
@ -38,4 +47,4 @@ Send us an email to report any security related bugs or vulnerabilities at [secu
You can encrypt your email message using our public PGP key.
Public key fingerprint: `1299 28C1 E79F E011 6DA4 C80F 8DB7 FD0F 61E6 ED76`
Public key fingerprint: `1299 28C1 E79F E011 6DA4 C80F 8DB7 FD0F 61E6 ED76`

View File

@ -29,7 +29,24 @@ func (g *AddressGenerator) Addresses() map[string]signingDetails {
return g.addrs
}
func (g *AddressGenerator) Generate() {
// Stream returns a channel that emits all addresses generated.
func (g *AddressGenerator) Stream() chan libwallet.MuunAddress {
ch := make(chan libwallet.MuunAddress)
go func() {
g.generate()
for _, details := range g.Addresses() {
ch <- details.Address
}
close(ch)
}()
return ch
}
func (g *AddressGenerator) generate() {
g.generateChangeAddrs()
g.generateExternalAddrs()
g.generateContactAddrs(100)

View File

@ -71,7 +71,7 @@ type BroadcastResponse struct {
type UnspentRef struct {
TxHash string `json:"tx_hash"`
TxPos int `json:"tx_pos"`
Value int `json:"value"`
Value int64 `json:"value"`
Height int `json:"height"`
}

7
go.mod
View File

@ -4,12 +4,9 @@ go 1.12
require (
github.com/btcsuite/btcd v0.21.0-beta
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
github.com/btcsuite/btcutil v1.0.2
github.com/btcsuite/btcwallet v0.11.1-0.20200612012534-48addcd5591a // indirect
github.com/btcsuite/btcwallet/walletdb v1.3.3 // indirect
github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200 // indirect
github.com/muun/libwallet v0.7.0
github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/muun/libwallet v0.8.0
)
replace github.com/lightninglabs/neutrino => github.com/muun/neutrino v0.0.0-20190914162326-7082af0fa257

24
go.sum
View File

@ -2,7 +2,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
cloud.google.com/go v0.33.1/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
git.schwanenlied.me/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e/go.mod h1:BWqTsj8PgcPriQJGl7el20J/7TuT1d/hSyFDXMEpoEo=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e/go.mod h1:Bdzq+51GR4/0DIhaICZEOm+OHvXGwwB2trKZ8B4Y6eQ=
github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82/go.mod h1:GbuBk21JqF+driLX3XtJYNZjGa45YDoa9IqCTzNSfEc=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
@ -192,6 +191,8 @@ github.com/lightningnetwork/lnd/queue v1.0.4 h1:8Dq3vxAFSACPy+pKN88oPFhuCpCoAACh
github.com/lightningnetwork/lnd/queue v1.0.4/go.mod h1:YTkTVZCxz8tAYreH27EO3s8572ODumWrNdYW2E/YKxg=
github.com/lightningnetwork/lnd/ticker v1.0.0 h1:S1b60TEGoTtCe2A0yeB+ecoj/kkS4qpwh6l+AkQEZwU=
github.com/lightningnetwork/lnd/ticker v1.0.0/go.mod h1:iaLXJiVgI1sPANIF2qYYUJXjoksPNvGNYowB8aRbpX0=
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw=
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796/go.mod h1:3p7ZTf9V1sNPI5H8P3NkTFF4LuwMdPl2DodF60qAKqY=
github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6/go.mod h1:8Vg/LTOO0KYa/vlHWJ6XZAevPQThGH5sufO0Hrou/lA=
@ -205,11 +206,10 @@ github.com/miekg/dns v0.0.0-20171125082028-79bfde677fa8/go.mod h1:W1PPwlIAgtquWB
github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg=
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/muun/libwallet v0.7.0 h1:FfPt+L7WN02qIgG9oJgVc9wBs7fw9w6PgHOsEI56o60=
github.com/muun/libwallet v0.7.0/go.mod h1:CB5ooFhTjbewO1YlP74Hnlf1PHWZhTU58g7LU3c2+fw=
github.com/muun/libwallet v0.8.0 h1:TtMsKr5O8OWUW5khZHpptokkKuPkXhOThLZ/ck4jXPM=
github.com/muun/libwallet v0.8.0/go.mod h1:fzmqBImU+ktQ5YDCM1MwXBl6vARC+73/ILGJMU/u96w=
github.com/muun/neutrino v0.0.0-20190914162326-7082af0fa257 h1:NW17wq2gZlEFeW3/Zx3wSmqlD0wKGf7YvhpP+CNCsbE=
github.com/muun/neutrino v0.0.0-20190914162326-7082af0fa257/go.mod h1:awTrhbCWjWNH4yVwZ4IE7nZbvpQ27e7OyD+jao7wRxA=
github.com/muun/recovery v0.3.0 h1:YyCXcuGx+SluVa0bHsyaXiowB67rdpJ6AudKv8QGvEE=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@ -218,8 +218,8 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pdfcpu/pdfcpu v0.3.8 h1:wdKii186dzmr/aP/fkJl2s9yT3TZcwc1VqgfabNymGI=
github.com/pdfcpu/pdfcpu v0.3.8/go.mod h1:EfJ1EIo3n5+YlGF53DGe1yF1wQLiqK1eqGDN5LuKALs=
github.com/pdfcpu/pdfcpu v0.3.9 h1:gHPreswsOGwe1zViJxufbvNZf0xhK4mxj/r1CwLp958=
github.com/pdfcpu/pdfcpu v0.3.9/go.mod h1:EfJ1EIo3n5+YlGF53DGe1yF1wQLiqK1eqGDN5LuKALs=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -259,16 +259,12 @@ golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20190823064033-3a9bac650e44/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@ -276,11 +272,7 @@ golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTk
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20200720140940-1a48f808d81f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -329,12 +321,8 @@ golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -1,44 +1,64 @@
package main
import (
"encoding/hex"
log "log"
"fmt"
"github.com/btcsuite/btcutil/base58"
"github.com/muun/libwallet"
"github.com/muun/libwallet/emergencykit"
)
var defaultNetwork = libwallet.Mainnet()
func buildExtendedKeys(rawKey1, rawKey2, recoveryCode string) (
*libwallet.DecryptedPrivateKey,
*libwallet.DecryptedPrivateKey) {
func decodeKeysFromInput(rawKey1 string, rawKey2 string) ([]*libwallet.EncryptedPrivateKeyInfo, error) {
key1, err := libwallet.DecodeEncryptedPrivateKey(rawKey1)
if err != nil {
return nil, fmt.Errorf("failed to decode first key: %w", err)
}
// Always take the salt from the second key (the same salt was used, but our older key format
// is missing the salt on the first key):
salt := extractSalt(rawKey2)
key2, err := libwallet.DecodeEncryptedPrivateKey(rawKey2)
if err != nil {
return nil, fmt.Errorf("failed to decode second key: %w", err)
}
return []*libwallet.EncryptedPrivateKeyInfo{key1, key2}, nil
}
func decodeKeysFromMetadata(meta *emergencykit.Metadata) ([]*libwallet.EncryptedPrivateKeyInfo, error) {
decodedKeys := make([]*libwallet.EncryptedPrivateKeyInfo, len(meta.EncryptedKeys))
for i, metaKey := range meta.EncryptedKeys {
decodedKeys[i] = &libwallet.EncryptedPrivateKeyInfo{
Version: meta.Version,
Birthday: meta.BirthdayBlock,
EphPublicKey: metaKey.DhPubKey,
CipherText: metaKey.EncryptedPrivKey,
Salt: metaKey.Salt,
}
}
return decodedKeys, nil
}
func decryptKeys(encryptedKeys []*libwallet.EncryptedPrivateKeyInfo, recoveryCode string) ([]*libwallet.DecryptedPrivateKey, error) {
// Always take the salt from the second key (the same salt was used for all keys, but our legacy
// key format did not include it in the first key):
salt := encryptedKeys[1].Salt
decryptionKey, err := libwallet.RecoveryCodeToKey(recoveryCode, salt)
if err != nil {
log.Fatalf("failed to process recovery code: %v", err)
return nil, fmt.Errorf("failed to process recovery code: %w", err)
}
key1, err := decryptionKey.DecryptKey(rawKey1, defaultNetwork)
if err != nil {
log.Fatalf("failed to decrypt first key: %v", err)
decryptedKeys := make([]*libwallet.DecryptedPrivateKey, len(encryptedKeys))
for i, encryptedKey := range encryptedKeys {
decryptedKey, err := decryptionKey.DecryptKey(encryptedKey, defaultNetwork)
if err != nil {
return nil, fmt.Errorf("failed to decrypt key %d: %w", i, err)
}
decryptedKeys[i] = decryptedKey
}
key2, err := decryptionKey.DecryptKey(rawKey2, defaultNetwork)
if err != nil {
log.Fatalf("failed to decrypt second key: %v", err)
}
return key1, key2
}
func extractSalt(rawKey string) string {
bytes := base58.Decode(rawKey)
saltBytes := bytes[len(bytes)-8:]
return hex.EncodeToString(saltBytes)
return decryptedKeys, nil
}

428
main.go
View File

@ -1,58 +1,124 @@
package main
import (
"flag"
"fmt"
"log"
"os"
"regexp"
"strconv"
"strings"
"github.com/btcsuite/btcutil"
"github.com/logrusorgru/aurora"
"github.com/muun/libwallet"
"github.com/muun/libwallet/emergencykit"
"github.com/muun/recovery/scanner"
)
const version = "2.1.0"
func main() {
printWelcomeMessage()
// Pick up command-line arguments:
flag.Parse()
args := flag.Args()
recoveryCode := readRecoveryCode()
userRawKey := readKey("first encrypted private key")
muunRawKey := readKey("second encrypted private key")
userKey, muunKey := buildExtendedKeys(userRawKey, muunRawKey, recoveryCode)
userKey.Key.Path = "m/1'/1'"
sweepAddress := readSweepAddress()
fmt.Println("")
fmt.Println("\nStarting scan of all your addresses. This may take a while")
sweeper := Sweeper{
UserKey: userKey.Key,
MuunKey: muunKey.Key,
Birthday: muunKey.Birthday,
SweepAddress: sweepAddress,
// Ensure correct form:
if len(args) > 1 {
printUsage()
os.Exit(0)
}
utxos, err := sweeper.GetUTXOs()
// Welcome!
printWelcomeMessage()
// We're going to need a few things to move forward with the recovery process. Let's make a list
// so we keep them in mind:
var recoveryCode string
var encryptedKeys []*libwallet.EncryptedPrivateKeyInfo
var destinationAddress btcutil.Address
// First on our list is the Recovery Code. This is the time to go looking for that piece of paper:
recoveryCode = readRecoveryCode()
// Good! Now, on to those keys. We need to read them and decrypt them:
encryptedKeys, err := readBackupFromInputOrPDF(flag.Arg(0))
if err != nil {
exitWithError(err)
}
fmt.Println("")
if len(utxos) > 0 {
fmt.Printf("The recovery tool has found the following confirmed UTXOs:\n%v", utxos)
} else {
fmt.Printf("No confirmed UTXOs found")
fmt.Println()
return
decryptedKeys, err := decryptKeys(encryptedKeys, recoveryCode)
if err != nil {
exitWithError(err)
}
decryptedKeys[0].Key.Path = "m/1'/1'" // a little adjustment for legacy users.
// Finally, we need the destination address to sweep the funds:
destinationAddress = readAddress()
sayBlock(`
Starting scan of all possible addresses. This will take a few minutes.
`)
transactionID := doRecovery(decryptedKeys, destinationAddress)
sayBlock(`
Transaction sent! You can check the status here: https://blockstream.info/tx/%v
(it will appear in Blockstream after a short delay)
We appreciate all kinds of feedback. If you have any, send it to {blue contact@muun.com}
`, transactionID)
}
// doRecovery runs the scan & sweep process, and returns the ID of the broadcasted transaction.
func doRecovery(decryptedKeys []*libwallet.DecryptedPrivateKey, destinationAddress btcutil.Address) string {
addrGen := NewAddressGenerator(decryptedKeys[0].Key, decryptedKeys[1].Key)
utxoScanner := scanner.NewScanner()
addresses := addrGen.Stream()
sweeper := Sweeper{
UserKey: decryptedKeys[0].Key,
MuunKey: decryptedKeys[1].Key,
Birthday: decryptedKeys[1].Birthday,
SweepAddress: destinationAddress,
}
reports := utxoScanner.Scan(addresses)
say("► {white Finding servers...}")
var lastReport *scanner.Report
for lastReport = range reports {
printReport(lastReport)
}
fmt.Println()
fmt.Println()
if lastReport.Err != nil {
exitWithError(fmt.Errorf("error while scanning addresses: %w", lastReport.Err))
}
say("{green ✓ Scan complete}\n")
utxos := lastReport.UtxosFound
if len(utxos) == 0 {
sayBlock("No funds were discovered\n\n")
return ""
}
var total int64
for _, utxo := range utxos {
total += utxo.Amount
say("• {white %d} sats in %s\n", utxo.Amount, utxo.Address.Address())
}
say("\n— {white %d} sats total\n", total)
txOutputAmount, txWeightInBytes, err := sweeper.GetSweepTxAmountAndWeightInBytes(utxos)
if err != nil {
printError(err)
exitWithError(err)
}
fee := readFee(txOutputAmount, txWeightInBytes)
@ -60,67 +126,89 @@ func main() {
// Then we re-build the sweep tx with the actual fee
sweepTx, err := sweeper.BuildSweepTx(utxos, fee)
if err != nil {
printError(err)
exitWithError(err)
}
fmt.Println("Transaction ready to be sent")
sayBlock("Sending transaction...")
err = sweeper.BroadcastTx(sweepTx)
if err != nil {
printError(err)
exitWithError(err)
}
fmt.Printf("Transaction sent! You can check the status here: https://blockstream.info/tx/%v", sweepTx.TxHash().String())
fmt.Println("")
fmt.Printf("We appreciate all kinds of feedback. If you have any, send it to contact@muun.com")
fmt.Println("")
return sweepTx.TxHash().String()
}
func printError(err error) {
log.Printf("The recovery tool failed with the following error: %v", err.Error())
log.Printf("")
log.Printf("You can try again or contact us at support@muun.com")
panic(err)
func exitWithError(err error) {
sayBlock(`
{red Error!}
The Recovery Tool encountered a problem. Please, try again.
If the problem persists, contact {blue support@muun.com} and include this:
{white error report}
%v
We're always there to help.
`, err)
os.Exit(1)
}
func printWelcomeMessage() {
fmt.Println("Welcome to Muun's Recovery Tool")
fmt.Println("")
fmt.Println("You can use this tool to transfer all funds from your Muun account to an")
fmt.Println("address of your choosing.")
fmt.Println("")
fmt.Println("To do this you will need:")
fmt.Println("1. Your Recovery Code, which you wrote down during your security setup")
fmt.Println("2. Your two encrypted private keys, which you exported from your wallet")
fmt.Println("3. A destination bitcoin address where all your funds will be sent")
fmt.Println("")
fmt.Println("If you have any questions, we'll be happy to answer them. Contact us at support@muun.com")
fmt.Println("")
say(`
{blue Muun Recovery Tool v%s}
To recover your funds, you will need:
1. {yellow Your Recovery Code}, which you wrote down during your security setup
2. {yellow Your Emergency Kit PDF}, which you exported from the app
3. {yellow Your destination bitcoin address}, where all your funds will be sent
If you have any questions, we'll be happy to answer them. Contact us at {blue support@muun.com}
`, version)
}
func printUsage() {
fmt.Println("Usage: recovery-tool [optional: path to Emergency Kit PDF]")
}
func printReport(report *scanner.Report) {
var total int64
for _, utxo := range report.UtxosFound {
total += utxo.Amount
}
say("\r► {white Scanned addresses}: %d | {white Sats found}: %d", report.ScannedAddresses, total)
}
func readRecoveryCode() string {
fmt.Println("")
fmt.Printf("Enter your Recovery Code")
fmt.Println()
fmt.Println("(it looks like this: 'ABCD-1234-POW2-R561-P120-JK26-12RW-45TT')")
fmt.Print("> ")
var userInput string
fmt.Scan(&userInput)
userInput = strings.TrimSpace(userInput)
sayBlock(`
{yellow Enter your Recovery Code}
(it looks like this: 'ABCD-1234-POW2-R561-P120-JK26-12RW-45TT')
`)
var userInput string
ask(&userInput)
userInput = strings.TrimSpace(userInput)
finalRC := strings.ToUpper(userInput)
if strings.Count(finalRC, "-") != 7 {
fmt.Printf("Invalid recovery code. Did you add the '-' separator between each 4-characters segment?")
fmt.Println()
fmt.Println("Please, try again")
say(`
Invalid recovery code. Did you add the '-' separator between each 4-characters segment?
Please, try again
`)
return readRecoveryCode()
}
if len(finalRC) != 39 {
fmt.Println("Your recovery code must have 39 characters")
fmt.Println("Please, try again")
say(`
Your recovery code must have 39 characters
Please, try again
`)
return readRecoveryCode()
}
@ -128,12 +216,68 @@ func readRecoveryCode() string {
return finalRC
}
func readBackupFromInputOrPDF(optionalPDF string) ([]*libwallet.EncryptedPrivateKeyInfo, error) {
// Here we have two possible flows, depending on whether the PDF was provided (pick up the
// encrypted backup automatically) or not (manual input). If we try for the automatic flow and fail,
// we can fall back to the manual one.
// Read metadata from the PDF, if given:
if optionalPDF != "" {
encryptedKeys, err := readBackupFromPDF(optionalPDF)
if err == nil {
return encryptedKeys, nil
}
// Hmm. Okay, we'll confess and fall back to manual input.
say(`
Couldn't read the PDF automatically: %v
Please, enter your data manually
`, err)
}
// Ask for manual input, if we have no PDF or couldn't read it:
encryptedKeys, err := readBackupFromInput()
if err != nil {
return nil, err
}
return encryptedKeys, nil
}
func readBackupFromInput() ([]*libwallet.EncryptedPrivateKeyInfo, error) {
firstRawKey := readKey("first encrypted private key")
secondRawKey := readKey("second encrypted private key")
decodedKeys, err := decodeKeysFromInput(firstRawKey, secondRawKey)
if err != nil {
return nil, err
}
return decodedKeys, nil
}
func readBackupFromPDF(path string) ([]*libwallet.EncryptedPrivateKeyInfo, error) {
reader := &emergencykit.MetadataReader{SrcFile: path}
metadata, err := reader.ReadMetadata()
if err != nil {
return nil, err
}
decodedKeys, err := decodeKeysFromMetadata(metadata)
if err != nil {
return nil, err
}
return decodedKeys, nil
}
func readKey(keyType string) string {
fmt.Println("")
fmt.Printf("Enter your %v", keyType)
fmt.Println()
fmt.Println("(it looks like this: '9xzpc7y6sNtRvh8Fh...')")
fmt.Print("> ")
sayBlock(`
{yellow Enter your %v}
(it looks like this: '9xzpc7y6sNtRvh8Fh...')
`, keyType)
// NOTE:
// Users will most likely copy and paste their keys from the Emergency Kit PDF. In this case,
@ -144,51 +288,60 @@ func readKey(keyType string) string {
// Given the line lengths actually found in our Emergency Kits, we have a simple solution for now:
// scan a minimum length of characters. Pasing from current versions of the Emergency Kit will
// only go past a minimum length when the key being entered is complete, in all cases.
userInput := scanMultiline(libwallet.EncodedKeyLengthLegacy)
userInput := askMultiline(libwallet.EncodedKeyLengthLegacy)
if len(userInput) < libwallet.EncodedKeyLengthLegacy {
// This is obviously invalid. Other problems will be detected later on, during the actual
// decoding and decryption stage.
fmt.Println("The key you entered doesn't look valid\nPlease, try again")
say(`
The key you entered doesn't look valid
Please, try again
`)
return readKey(keyType)
}
return userInput
}
func readSweepAddress() btcutil.Address {
fmt.Println("")
fmt.Println("Enter your destination bitcoin address")
fmt.Print("> ")
func readAddress() btcutil.Address {
sayBlock(`
{yellow Enter your destination bitcoin address}
`)
var userInput string
fmt.Scan(&userInput)
ask(&userInput)
userInput = strings.TrimSpace(userInput)
addr, err := btcutil.DecodeAddress(userInput, &chainParams)
if err != nil {
fmt.Println("This is not a valid bitcoin address")
fmt.Println("")
fmt.Println("Please, try again")
say(`
This is not a valid bitcoin address
Please, try again
`)
return readSweepAddress()
return readAddress()
}
return addr
}
func readFee(totalBalance, weight int64) int64 {
fmt.Println("")
fmt.Printf("Enter the fee in satoshis per byte. Tx weight: %v bytes. You can check the status of the mempool here: https://bitcoinfees.earn.com/#fees", weight)
fmt.Println()
fmt.Println("(Example: 5)")
fmt.Print("> ")
sayBlock(`
{yellow Enter the fee rate (sats/byte)}
Your transaction weighs %v bytes. You can get suggestions in https://bitcoinfees.earn.com/#fees
`, weight)
var userInput string
fmt.Scan(&userInput)
ask(&userInput)
feeInSatsPerByte, err := strconv.ParseInt(userInput, 10, 64)
if err != nil || feeInSatsPerByte <= 0 {
fmt.Printf("The fee must be a number")
fmt.Println("")
fmt.Println("Please, try again")
say(`
The fee must be a whole number
Please, try again
`)
return readFee(totalBalance, weight)
}
@ -196,9 +349,10 @@ func readFee(totalBalance, weight int64) int64 {
totalFee := feeInSatsPerByte * weight
if totalBalance-totalFee < 546 {
fmt.Printf("The fee is too high. The amount left must be higher than dust")
fmt.Println("")
fmt.Println("Please, try again")
say(`
The fee is too high. The remaining amount after deducting is too low to send.
Please, try again
`)
return readFee(totalBalance, weight)
}
@ -207,32 +361,84 @@ func readFee(totalBalance, weight int64) int64 {
}
func readConfirmation(value, fee int64, address string) {
fmt.Println("")
fmt.Printf("About to send %v satoshis with fee: %v satoshis to %v", value, fee, address)
fmt.Println()
fmt.Println("Confirm? (y/n)")
fmt.Print("> ")
sayBlock(`
{whiteUnderline Summary}
{white Amount}: %v sats
{white Fee}: %v sats
{white Destination}: %v
{yellow Confirm?} (y/n)
`, value, fee, address)
var userInput string
fmt.Scan(&userInput)
ask(&userInput)
if userInput == "y" || userInput == "Y" {
return
}
if userInput == "n" || userInput == "N" {
log.Println()
log.Printf("Recovery tool stopped")
log.Println()
log.Printf("You can try again or contact us at support@muun.com")
sayBlock(`
Recovery tool stopped
You can try again or contact us at {blue support@muun.com}
`)
os.Exit(1)
}
fmt.Println()
fmt.Println("You can only enter 'y' to accept or 'n' to cancel")
say(`You can only enter 'y' to confirm or 'n' to cancel`)
fmt.Print("\n\n")
readConfirmation(value, fee, address)
}
func scanMultiline(minChars int) string {
var leadingIndentRe = regexp.MustCompile("^[ \t]+")
var colorRe = regexp.MustCompile(`\{(\w+?) ([^\}]+?)\}`)
func say(message string, v ...interface{}) {
noEmptyLine := strings.TrimLeft(message, " \n")
firstIndent := leadingIndentRe.FindString(noEmptyLine)
noIndent := strings.ReplaceAll(noEmptyLine, firstIndent, "")
noTrailingSpace := strings.TrimRight(noIndent, " \t")
withColors := colorRe.ReplaceAllStringFunc(noTrailingSpace, func(match string) string {
groups := colorRe.FindStringSubmatch(match)
return applyColor(groups[1], groups[2])
})
fmt.Printf(withColors, v...)
}
func sayBlock(message string, v ...interface{}) {
fmt.Println()
say(message, v...)
}
func applyColor(colorName string, text string) string {
boldText := aurora.Bold(text) // in most terminals, bold colors are prettier and highlight better
switch colorName {
case "red":
return aurora.Red(boldText).String()
case "blue":
return aurora.Blue(boldText).String()
case "yellow":
return aurora.Yellow(boldText).String()
case "green":
return aurora.Green(boldText).String()
case "white":
return aurora.White(boldText).String()
case "whiteUnderline":
return aurora.Underline(boldText).White().String()
}
panic("No such color: " + colorName)
}
func askMultiline(minChars int) string {
fmt.Print("➜ ")
var result strings.Builder
for result.Len() < minChars {
@ -245,7 +451,7 @@ func scanMultiline(minChars int) string {
return result.String()
}
func exitWithError(reason error) {
fmt.Println("\nError while scanning. Can't continue. Please, try again later.")
os.Exit(1)
func ask(result *string) {
fmt.Print("➜ ")
fmt.Scan(result)
}

7
recovery-tool Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
# Move to the repository root:
cd "$(dirname "${BASH_SOURCE[0]}")"
# Go!
go run -mod=vendor . -- "$@"

View File

@ -2,51 +2,59 @@ package main
import (
"bytes"
"fmt"
"encoding/hex"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/muun/libwallet"
"github.com/muun/recovery/scanner"
)
func buildSweepTx(utxos []*RelevantTx, sweepAddress btcutil.Address, fee int64) []byte {
func buildSweepTx(utxos []*scanner.Utxo, sweepAddress btcutil.Address, fee int64) ([]byte, error) {
tx := wire.NewMsgTx(2)
value := int64(0)
for _, utxo := range utxos {
tx.AddTxIn(wire.NewTxIn(&utxo.Outpoint, []byte{}, [][]byte{}))
value += utxo.Satoshis
}
chainHash, err := chainhash.NewHashFromStr(utxo.TxID)
if err != nil {
return nil, err
}
fmt.Println()
fmt.Printf("Total balance in satoshis: %v", value)
fmt.Println()
outpoint := wire.OutPoint{
Hash: *chainHash,
Index: uint32(utxo.OutputIndex),
}
tx.AddTxIn(wire.NewTxIn(&outpoint, []byte{}, [][]byte{}))
value += utxo.Amount
}
value -= fee
script, err := txscript.PayToAddrScript(sweepAddress)
if err != nil {
printError(err)
return nil, err
}
tx.AddTxOut(wire.NewTxOut(value, script))
writer := &bytes.Buffer{}
err = tx.Serialize(writer)
if err != nil {
panic(err)
return nil, err
}
if fee != 0 {
readConfirmation(value, fee, sweepAddress.String())
}
return writer.Bytes()
return writer.Bytes(), nil
}
func buildSignedTx(utxos []*RelevantTx, sweepTx []byte, userKey *libwallet.HDPrivateKey,
func buildSignedTx(utxos []*scanner.Utxo, sweepTx []byte, userKey *libwallet.HDPrivateKey,
muunKey *libwallet.HDPrivateKey) (*wire.MsgTx, error) {
inputList := &libwallet.InputList{}
@ -59,7 +67,7 @@ func buildSignedTx(utxos []*RelevantTx, sweepTx []byte, userKey *libwallet.HDPri
pstx, err := libwallet.NewPartiallySignedTransaction(inputList, sweepTx)
if err != nil {
printError(err)
return nil, err
}
signedTx, err := pstx.FullySign(userKey, muunKey)
@ -72,17 +80,18 @@ func buildSignedTx(utxos []*RelevantTx, sweepTx []byte, userKey *libwallet.HDPri
return wireTx, nil
}
// input is a minimal type that implements libwallet.Input
type input struct {
tx *RelevantTx
utxo *scanner.Utxo
muunSignature []byte
}
func (i *input) OutPoint() libwallet.Outpoint {
return &outpoint{tx: i.tx}
return &outpoint{utxo: i.utxo}
}
func (i *input) Address() libwallet.MuunAddress {
return i.tx.SigningDetails.Address
return i.utxo.Address
}
func (i *input) UserSignature() []byte {
@ -105,18 +114,24 @@ func (i *input) IncomingSwap() libwallet.InputIncomingSwap {
return nil
}
// outpoint is a minimal type that implements libwallet.Outpoint
type outpoint struct {
tx *RelevantTx
utxo *scanner.Utxo
}
func (o *outpoint) TxId() []byte {
return o.tx.Outpoint.Hash.CloneBytes()
raw, err := hex.DecodeString(o.utxo.TxID)
if err != nil {
panic(err) // we wrote this hex value ourselves, no input from anywhere else
}
return raw
}
func (o *outpoint) Index() int {
return int(o.tx.Outpoint.Index)
return o.utxo.OutputIndex
}
func (o *outpoint) Amount() int64 {
return o.tx.Satoshis
return o.utxo.Amount
}

View File

@ -39,22 +39,33 @@ type Scanner struct {
log *utils.Logger
}
// Report contains information about an ongoing scan.
type Report struct {
ScannedAddresses int
UtxosFound []*Utxo
Err error
}
// Utxo references a transaction output, plus the associated MuunAddress and script.
type Utxo struct {
TxID string
OutputIndex int
Amount int
Amount int64
Address libwallet.MuunAddress
Script []byte
}
// scanContext contains the synchronization objects for a single Scanner round, to manage Tasks.
type scanContext struct {
// Task management:
addresses chan libwallet.MuunAddress
results chan Utxo
errors chan error
results chan *scanTaskResult
done chan struct{}
wg *sync.WaitGroup
// Progress reporting:
reports chan *Report
reportCache *Report
}
// NewScanner creates an initialized Scanner.
@ -67,34 +78,54 @@ func NewScanner() *Scanner {
}
// Scan an address space and return all relevant transactions for a sweep.
func (s *Scanner) Scan(addresses chan libwallet.MuunAddress) ([]Utxo, error) {
var results []Utxo
func (s *Scanner) Scan(addresses chan libwallet.MuunAddress) <-chan *Report {
var waitGroup sync.WaitGroup
// Create the Context that goroutines will share:
ctx := &scanContext{
addresses: addresses,
results: make(chan Utxo),
errors: make(chan error),
results: make(chan *scanTaskResult),
done: make(chan struct{}),
wg: &waitGroup,
reports: make(chan *Report),
reportCache: &Report{
ScannedAddresses: 0,
UtxosFound: []*Utxo{},
},
}
// Start the scan in background:
go s.startCollect(ctx)
go s.startScan(ctx)
return ctx.reports
}
func (s *Scanner) startCollect(ctx *scanContext) {
// Collect all results until the done signal, or abort on the first error:
for {
select {
case err := <-ctx.errors:
close(ctx.done) // send the done signal ourselves
return nil, err
case result := <-ctx.results:
results = append(results, result)
newReport := *ctx.reportCache // create a new private copy
ctx.reportCache = &newReport
if result.Err != nil {
ctx.reportCache.Err = s.log.Errorf("Scan failed: %w", result.Err)
ctx.reports <- ctx.reportCache
close(ctx.done) // failed after several retries, we give up and terminate all tasks
close(ctx.reports) // close the report channel to let callers know we're done
return
}
ctx.reportCache.ScannedAddresses += len(result.Task.addresses)
ctx.reportCache.UtxosFound = append(ctx.reportCache.UtxosFound, result.Utxos...)
ctx.reports <- ctx.reportCache
case <-ctx.done:
return results, nil
close(ctx.reports) // close the report channel to let callers know we're done
return
}
}
}
@ -148,18 +179,8 @@ func (s *Scanner) scanBatch(ctx *scanContext, client *electrum.Client, batch []l
exit: ctx.done,
}
// Do the thing:
addressResults, err := task.Execute()
if err != nil {
ctx.errors <- s.log.Errorf("Scan failed: %w", err)
return
}
// Send back all results:
for _, result := range addressResults {
ctx.results <- result
}
// Do the thing and send back the result:
ctx.results <- task.Execute()
}
func streamBatches(addresses chan libwallet.MuunAddress) chan []libwallet.MuunAddress {

View File

@ -20,10 +20,16 @@ type scanTask struct {
exit chan struct{}
}
// scanTaskResult contains a summary of the execution of a task.
type scanTaskResult struct {
Task *scanTask
Utxos []*Utxo
Err error
}
// Execute obtains the Utxo set for the Task address, implementing a retry strategy.
func (t *scanTask) Execute() ([]Utxo, error) {
results := make(chan []Utxo)
errors := make(chan error)
func (t *scanTask) Execute() *scanTaskResult {
results := make(chan *scanTaskResult)
timeout := time.After(t.timeout)
// Keep the last error around, in case we reach the timeout and want to know the reason:
@ -31,61 +37,60 @@ func (t *scanTask) Execute() ([]Utxo, error) {
for {
// Attempt to run the task:
go t.tryExecuteAsync(results, errors)
go t.tryExecuteAsync(results)
// Wait until a result is sent, the timeout is reached or the task canceled, capturing errors
// errors along the way:
select {
case <-t.exit:
return []Utxo{}, nil // stop retrying when we get the done signal
return t.exitResult() // stop retrying when we get the done signal
case result := <-results:
return result, nil
if result.Err == nil {
return result // we're done! nice work everyone.
}
case err := <-errors:
lastError = err
lastError = result.Err // keep retrying when an attempt fails
case <-timeout:
return nil, fmt.Errorf("Task timed out. Last error: %w", lastError)
return t.errorResult(fmt.Errorf("Task timed out. Last error: %w", lastError)) // stop on timeout
}
}
}
func (t *scanTask) tryExecuteAsync(results chan []Utxo, errors chan error) {
func (t *scanTask) tryExecuteAsync(results chan *scanTaskResult) {
// Errors will almost certainly arise from Electrum server failures, which are extremely
// common. Unreachable IPs, dropped connections, sudden EOFs, etc. We'll run this task, assuming
// the servers are at fault when something fails, disconnecting and cycling them as we retry.
result, err := t.tryExecute()
result := t.tryExecute()
if err != nil {
if result.Err != nil {
t.client.Disconnect()
errors <- err
return
}
results <- result
}
func (t *scanTask) tryExecute() ([]Utxo, error) {
func (t *scanTask) tryExecute() *scanTaskResult {
// If our client is not connected, make an attempt to connect to a server:
if !t.client.IsConnected() {
err := t.client.Connect(t.servers.NextServer())
if err != nil {
return nil, err
return t.errorResult(err)
}
}
// Prepare the output scripts for all given addresses:
outputScripts, err := getOutputScripts(t.addresses)
if err != nil {
return nil, err
return t.errorResult(err)
}
// Prepare the index hashes that Electrum requires to list outputs:
indexHashes, err := getIndexHashes(outputScripts)
if err != nil {
return nil, err
return t.errorResult(err)
}
// Call Electrum to get the unspent output list, grouped by index for each address:
@ -98,15 +103,15 @@ func (t *scanTask) tryExecute() ([]Utxo, error) {
}
if err != nil {
return nil, err
return t.errorResult(err)
}
// Compile the results into a list of `Utxos`:
var utxos []Utxo
var utxos []*Utxo
for i, unspentRefGroup := range unspentRefGroups {
for _, unspentRef := range unspentRefGroup {
newUtxo := Utxo{
newUtxo := &Utxo{
TxID: unspentRef.TxHash,
OutputIndex: unspentRef.TxPos,
Amount: unspentRef.Value,
@ -118,7 +123,7 @@ func (t *scanTask) tryExecute() ([]Utxo, error) {
}
}
return utxos, nil
return t.successResult(utxos)
}
func (t *scanTask) listUnspentWithBatching(indexHashes []string) ([][]electrum.UnspentRef, error) {
@ -145,6 +150,18 @@ func (t *scanTask) listUnspentWithoutBatching(indexHashes []string) ([][]electru
return unspentRefGroups, nil
}
func (t *scanTask) errorResult(err error) *scanTaskResult {
return &scanTaskResult{Task: t, Err: err}
}
func (t *scanTask) successResult(utxos []*Utxo) *scanTaskResult {
return &scanTaskResult{Task: t, Utxos: utxos}
}
func (t *scanTask) exitResult() *scanTaskResult {
return &scanTaskResult{Task: t}
}
// getIndexHashes calculates all the Electrum index hashes for a list of output scripts.
func getIndexHashes(outputScripts [][]byte) ([]string, error) {
indexHashes := make([]string, len(outputScripts))

View File

@ -9,7 +9,6 @@ import (
"github.com/muun/recovery/scanner"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
@ -27,55 +26,7 @@ type Sweeper struct {
SweepAddress btcutil.Address
}
// RelevantTx contains a PKScipt, an Address an a boolean to check if its spent or not
type RelevantTx struct {
PkScript []byte
Address string
Spent bool
Satoshis int64
SigningDetails signingDetails
Outpoint wire.OutPoint
}
func (tx *RelevantTx) String() string {
return fmt.Sprintf("outpoint %v:%v for %v sats on path %v",
tx.Outpoint.Hash, tx.Outpoint.Index, tx.Satoshis, tx.SigningDetails.Address.DerivationPath())
}
func (s *Sweeper) GetUTXOs() ([]*RelevantTx, error) {
addresses := s.generateAddresses()
results, err := scanner.NewScanner().Scan(addresses)
if err != nil {
return nil, fmt.Errorf("error while scanning addresses: %w", err)
}
txs, err := buildRelevantTxs(results)
if err != nil {
return nil, fmt.Errorf("error while crafting transaction: %w", err)
}
return txs, nil
}
func (s *Sweeper) generateAddresses() chan libwallet.MuunAddress {
ch := make(chan libwallet.MuunAddress)
go func() {
g := NewAddressGenerator(s.UserKey, s.MuunKey)
g.Generate()
for _, details := range g.Addresses() {
ch <- details.Address
}
close(ch)
}()
return ch
}
func (s *Sweeper) GetSweepTxAmountAndWeightInBytes(utxos []*RelevantTx) (outputAmount int64, weightInBytes int64, err error) {
func (s *Sweeper) GetSweepTxAmountAndWeightInBytes(utxos []*scanner.Utxo) (outputAmount int64, weightInBytes int64, err error) {
// we build a sweep tx with 0 fee with the only purpose of checking its signed size
zeroFeeSweepTx, err := s.BuildSweepTx(utxos, 0)
if err != nil {
@ -88,12 +39,16 @@ func (s *Sweeper) GetSweepTxAmountAndWeightInBytes(utxos []*RelevantTx) (outputA
return outputAmount, weightInBytes, nil
}
func (s *Sweeper) BuildSweepTx(utxos []*RelevantTx, fee int64) (*wire.MsgTx, error) {
func (s *Sweeper) BuildSweepTx(utxos []*scanner.Utxo, fee int64) (*wire.MsgTx, error) {
derivedMuunKey, err := s.MuunKey.DeriveTo("m/1'/1'")
if err != nil {
return nil, err
}
sweepTx := buildSweepTx(utxos, s.SweepAddress, fee)
sweepTx, err := buildSweepTx(utxos, s.SweepAddress, fee)
if err != nil {
return nil, err
}
return buildSignedTx(utxos, sweepTx, s.UserKey, derivedMuunKey)
}
@ -124,33 +79,3 @@ func (s *Sweeper) BroadcastTx(tx *wire.MsgTx) error {
return nil
}
// buildRelevantTxs prepares the output from Scanner for crafting.
func buildRelevantTxs(utxos []scanner.Utxo) ([]*RelevantTx, error) {
var relevantTxs []*RelevantTx
for _, utxo := range utxos {
address := utxo.Address.Address()
chainHash, err := chainhash.NewHashFromStr(utxo.TxID)
if err != nil {
return nil, err
}
relevantTx := &RelevantTx{
PkScript: utxo.Script,
Address: address,
Spent: false,
Satoshis: int64(utxo.Amount),
SigningDetails: signingDetails{utxo.Address},
Outpoint: wire.OutPoint{
Hash: *chainHash,
Index: uint32(utxo.OutputIndex),
},
}
relevantTxs = append(relevantTxs, relevantTx)
}
return relevantTxs, nil
}

34
vendor/github.com/logrusorgru/aurora/.gitignore generated vendored Normal file
View File

@ -0,0 +1,34 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
*.out
# coverage
cover.html
# benchcmp
*.cmp

9
vendor/github.com/logrusorgru/aurora/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,9 @@
language: go
go:
- tip
before_install:
- go get github.com/axw/gocov/gocov
- go get github.com/mattn/goveralls
- go get golang.org/x/tools/cmd/cover
script:
- $HOME/gopath/bin/goveralls -service=travis-ci

8
vendor/github.com/logrusorgru/aurora/AUTHORS.md generated vendored Normal file
View File

@ -0,0 +1,8 @@
AUTHORS
=======
- Konstantin Ivanov @logrusorgru
- Mattias Eriksson @snaggen
- Ousmane Traore @otraore
- Simon Legner @simon04
- Sevenate @sevenate

59
vendor/github.com/logrusorgru/aurora/CHANGELOG.md generated vendored Normal file
View File

@ -0,0 +1,59 @@
Changes
=======
---
16:05:02
Thursday, July 2, 2020
Change license from the WTFPL to the Unlicense due to pkg.go.dev restriction.
---
15:39:40
Wednesday, April 17, 2019
- Bright background and foreground colors
- 8-bit indexed colors `Index`, `BgIndex`
- 24 grayscale colors `Gray`, `BgGray`
- `Yellow` and `BgYellow` methods, mark Brow and BgBrown as deprecated
Following specifications, correct name of the colors are yellow, but
by historical reason they are called brown. Both, the `Yellow` and the
`Brown` methods (including `Bg+`) represents the same colors. The Brown
are leaved for backward compatibility until Go modules production release.
- Additional formats
+ `Faint` that is opposite to the `Bold`
+ `DoublyUnderline`
+ `Fraktur`
+ `Italic`
+ `Underline`
+ `SlowBlink` with `Blink` alias
+ `RapidBlink`
+ `Reverse` that is alias for the `Inverse`
+ `Conceal` with `Hidden` alias
+ `CrossedOut` with `StrikeThrough` alias
+ `Framed`
+ `Encircled`
+ `Overlined`
- Add AUTHORS.md file and change all copyright notices.
- `Reset` method to create clear value. `Reset` method that replaces
`Bleach` method. The `Bleach` method was marked as deprecated.
---
14:25:49
Friday, August 18, 2017
- LICENSE.md changed to LICENSE
- fix email in README.md
- add "no warranty" to README.md
- set proper copyright date
---
16:59:28
Tuesday, November 8, 2016
- Rid out off sync.Pool
- Little optimizations (very little)
- Improved benchmarks
---

24
vendor/github.com/logrusorgru/aurora/LICENSE generated vendored Normal file
View File

@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>

314
vendor/github.com/logrusorgru/aurora/README.md generated vendored Normal file
View File

@ -0,0 +1,314 @@
Aurora
======
[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white)](https://pkg.go.dev/github.com/logrusorgru/aurora?tab=doc)
[![Unlicense](https://img.shields.io/badge/license-unlicense-blue.svg)](http://unlicense.org/)
[![Build Status](https://travis-ci.org/logrusorgru/aurora.svg)](https://travis-ci.org/logrusorgru/aurora)
[![Coverage Status](https://coveralls.io/repos/logrusorgru/aurora/badge.svg?branch=master)](https://coveralls.io/r/logrusorgru/aurora?branch=master)
[![GoReportCard](https://goreportcard.com/badge/logrusorgru/aurora)](https://goreportcard.com/report/logrusorgru/aurora)
[![Gitter](https://img.shields.io/badge/chat-on_gitter-46bc99.svg?logo=data:image%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTQiIHdpZHRoPSIxNCI%2BPGcgZmlsbD0iI2ZmZiI%2BPHJlY3QgeD0iMCIgeT0iMyIgd2lkdGg9IjEiIGhlaWdodD0iNSIvPjxyZWN0IHg9IjIiIHk9IjQiIHdpZHRoPSIxIiBoZWlnaHQ9IjciLz48cmVjdCB4PSI0IiB5PSI0IiB3aWR0aD0iMSIgaGVpZ2h0PSI3Ii8%2BPHJlY3QgeD0iNiIgeT0iNCIgd2lkdGg9IjEiIGhlaWdodD0iNCIvPjwvZz48L3N2Zz4%3D&logoWidth=10)](https://gitter.im/logrusorgru/aurora)
Ultimate ANSI colors for Golang. The package supports Printf/Sprintf etc.
![aurora logo](https://github.com/logrusorgru/aurora/blob/master/gopher_aurora.png)
# TOC
- [Installation](#installation)
- [Usage](#usage)
+ [Simple](#simple)
+ [Printf](#printf)
+ [aurora.Sprintf](#aurorasprintf)
+ [Enable/Disable colors](#enabledisable-colors)
- [Chains](#chains)
- [Colorize](#colorize)
- [Grayscale](#grayscale)
- [8-bit colors](#8-bit-colors)
- [Supported Colors & Formats](#supported-colors--formats)
+ [All colors](#all-colors)
+ [Standard and bright colors](#standard-and-bright-colors)
+ [Formats are likely supported](#formats-are-likely-supported)
+ [Formats are likely unsupported](#formats-are-likely-unsupported)
- [Limitations](#limitations)
+ [Windows](#windows)
+ [TTY](#tty)
- [Licensing](#licensing)
# Installation
Get
```
go get -u github.com/logrusorgru/aurora
```
Test
```
go test -cover github.com/logrusorgru/aurora
```
# Usage
### Simple
```go
package main
import (
"fmt"
. "github.com/logrusorgru/aurora"
)
func main() {
fmt.Println("Hello,", Magenta("Aurora"))
fmt.Println(Bold(Cyan("Cya!")))
}
```
![simple png](https://github.com/logrusorgru/aurora/blob/master/simple.png)
### Printf
```go
package main
import (
"fmt"
. "github.com/logrusorgru/aurora"
)
func main() {
fmt.Printf("Got it %d times\n", Green(1240))
fmt.Printf("PI is %+1.2e\n", Cyan(3.14))
}
```
![printf png](https://github.com/logrusorgru/aurora/blob/master/printf.png)
### aurora.Sprintf
```go
package main
import (
"fmt"
. "github.com/logrusorgru/aurora"
)
func main() {
fmt.Println(Sprintf(Magenta("Got it %d times"), Green(1240)))
}
```
![sprintf png](https://github.com/logrusorgru/aurora/blob/master/sprintf.png)
### Enable/Disable colors
```go
package main
import (
"fmt"
"flag"
"github.com/logrusorgru/aurora"
)
// colorizer
var au aurora.Aurora
var colors = flag.Bool("colors", false, "enable or disable colors")
func init() {
flag.Parse()
au = aurora.NewAurora(*colors)
}
func main() {
// use colorizer
fmt.Println(au.Green("Hello"))
}
```
Without flags:
![disable png](https://github.com/logrusorgru/aurora/blob/master/disable.png)
With `-colors` flag:
![enable png](https://github.com/logrusorgru/aurora/blob/master/enable.png)
# Chains
The following samples are equal
```go
x := BgMagenta(Bold(Red("x")))
```
```go
x := Red("x").Bold().BgMagenta()
```
The second is more readable
# Colorize
There is `Colorize` function that allows to choose some colors and
format from a side
```go
func getColors() Color {
// some stuff that returns appropriate colors and format
}
// [...]
func main() {
fmt.Println(Colorize("Greeting", getColors()))
}
```
Less complicated example
```go
x := Colorize("Greeting", GreenFg|GrayBg|BoldFm)
```
Unlike other color functions and methods (such as Red/BgBlue etc)
a `Colorize` clears previous colors
```go
x := Red("x").Colorize(BgGreen) // will be with green background only
```
# Grayscale
```go
fmt.Println(" ",
Gray(1-1, " 00-23 ").BgGray(24-1),
Gray(4-1, " 03-19 ").BgGray(20-1),
Gray(8-1, " 07-15 ").BgGray(16-1),
Gray(12-1, " 11-11 ").BgGray(12-1),
Gray(16-1, " 15-07 ").BgGray(8-1),
Gray(20-1, " 19-03 ").BgGray(4-1),
Gray(24-1, " 23-00 ").BgGray(1-1),
)
```
![grayscale png](https://github.com/logrusorgru/aurora/blob/master/aurora_grayscale.png)
# 8-bit colors
Methods `Index` and `BgIndex` implements 8-bit colors.
| Index/BgIndex | Meaning | Foreground | Background |
| -------------- | --------------- | ---------- | ---------- |
| 0- 7 | standard colors | 30- 37 | 40- 47 |
| 8- 15 | bright colors | 90- 97 | 100-107 |
| 16-231 | 216 colors | 38;5;n | 48;5;n |
| 232-255 | 24 grayscale | 38;5;n | 48;5;n |
# Supported colors & formats
- formats
+ bold (1)
+ faint (2)
+ doubly-underline (21)
+ fraktur (20)
+ italic (3)
+ underline (4)
+ slow blink (5)
+ rapid blink (6)
+ reverse video (7)
+ conceal (8)
+ crossed out (9)
+ framed (51)
+ encircled (52)
+ overlined (53)
- background and foreground colors, including bright
+ black
+ red
+ green
+ yellow (brown)
+ blue
+ magenta
+ cyan
+ white
+ 24 grayscale colors
+ 216 8-bit colors
### All colors
![linux png](https://github.com/logrusorgru/aurora/blob/master/aurora_colors_black.png)
![white png](https://github.com/logrusorgru/aurora/blob/master/aurora_colors_white.png)
### Standard and bright colors
![linux black standard png](https://github.com/logrusorgru/aurora/blob/master/aurora_black_standard.png)
![linux white standard png](https://github.com/logrusorgru/aurora/blob/master/aurora_white_standard.png)
### Formats are likely supported
![formats supported gif](https://github.com/logrusorgru/aurora/blob/master/aurora_formats.gif)
### Formats are likely unsupported
![formats rarely supported png](https://github.com/logrusorgru/aurora/blob/master/aurora_rarely_supported.png)
# Limitations
There is no way to represent `%T` and `%p` with colors using
a standard approach
```go
package main
import (
"fmt"
. "github.com/logrusorgru/aurora"
)
func main() {
r := Red("red")
var i int
fmt.Printf("%T %p\n", r, Green(&i))
}
```
Output will be without colors
```
aurora.value %!p(aurora.value={0xc42000a310 768 0})
```
The obvious workaround is `Red(fmt.Sprintf("%T", some))`
### Windows
The Aurora provides ANSI colors only, so there is no support for Windows. That said, there are workarounds available.
Check out these comments to learn more:
- [Using go-colorable](https://github.com/logrusorgru/aurora/issues/2#issuecomment-299014211).
- [Using registry for Windows 10](https://github.com/logrusorgru/aurora/issues/10#issue-476361247).
### TTY
The Aurora has no internal TTY detectors by design. Take a look
[this comment](https://github.com/logrusorgru/aurora/issues/2#issuecomment-299030108) if you want turn
on colors for a terminal only, and turn them off for a file.
### Licensing
Copyright &copy; 2016-2020 The Aurora Authors. This work is free.
It comes without any warranty, to the extent permitted by applicable
law. You can redistribute it and/or modify it under the terms of the
the Unlicense. See the LICENSE file for more details.

725
vendor/github.com/logrusorgru/aurora/aurora.go generated vendored Normal file
View File

@ -0,0 +1,725 @@
//
// Copyright (c) 2016-2020 The Aurora Authors. All rights reserved.
// This program is free software. It comes without any warranty,
// to the extent permitted by applicable law. You can redistribute
// it and/or modify it under the terms of the Unlicense. See LICENSE
// file for more details or see below.
//
//
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <http://unlicense.org/>
//
// Package aurora implements ANSI-colors
package aurora
import (
"fmt"
)
// An Aurora implements colorizer interface.
// It also can be a non-colorizer
type Aurora interface {
// Reset wraps given argument returning Value
// without formats and colors.
Reset(arg interface{}) Value
//
// Formats
//
//
// Bold or increased intensity (1).
Bold(arg interface{}) Value
// Faint, decreased intensity (2).
Faint(arg interface{}) Value
//
// DoublyUnderline or Bold off, double-underline
// per ECMA-48 (21).
DoublyUnderline(arg interface{}) Value
// Fraktur, rarely supported (20).
Fraktur(arg interface{}) Value
//
// Italic, not widely supported, sometimes
// treated as inverse (3).
Italic(arg interface{}) Value
// Underline (4).
Underline(arg interface{}) Value
//
// SlowBlink, blinking less than 150
// per minute (5).
SlowBlink(arg interface{}) Value
// RapidBlink, blinking 150+ per minute,
// not widely supported (6).
RapidBlink(arg interface{}) Value
// Blink is alias for the SlowBlink.
Blink(arg interface{}) Value
//
// Reverse video, swap foreground and
// background colors (7).
Reverse(arg interface{}) Value
// Inverse is alias for the Reverse
Inverse(arg interface{}) Value
//
// Conceal, hidden, not widely supported (8).
Conceal(arg interface{}) Value
// Hidden is alias for the Conceal
Hidden(arg interface{}) Value
//
// CrossedOut, characters legible, but
// marked for deletion (9).
CrossedOut(arg interface{}) Value
// StrikeThrough is alias for the CrossedOut.
StrikeThrough(arg interface{}) Value
//
// Framed (51).
Framed(arg interface{}) Value
// Encircled (52).
Encircled(arg interface{}) Value
//
// Overlined (53).
Overlined(arg interface{}) Value
//
// Foreground colors
//
//
// Black foreground color (30)
Black(arg interface{}) Value
// Red foreground color (31)
Red(arg interface{}) Value
// Green foreground color (32)
Green(arg interface{}) Value
// Yellow foreground color (33)
Yellow(arg interface{}) Value
// Brown foreground color (33)
//
// Deprecated: use Yellow instead, following specification
Brown(arg interface{}) Value
// Blue foreground color (34)
Blue(arg interface{}) Value
// Magenta foreground color (35)
Magenta(arg interface{}) Value
// Cyan foreground color (36)
Cyan(arg interface{}) Value
// White foreground color (37)
White(arg interface{}) Value
//
// Bright foreground colors
//
// BrightBlack foreground color (90)
BrightBlack(arg interface{}) Value
// BrightRed foreground color (91)
BrightRed(arg interface{}) Value
// BrightGreen foreground color (92)
BrightGreen(arg interface{}) Value
// BrightYellow foreground color (93)
BrightYellow(arg interface{}) Value
// BrightBlue foreground color (94)
BrightBlue(arg interface{}) Value
// BrightMagenta foreground color (95)
BrightMagenta(arg interface{}) Value
// BrightCyan foreground color (96)
BrightCyan(arg interface{}) Value
// BrightWhite foreground color (97)
BrightWhite(arg interface{}) Value
//
// Other
//
// Index of pre-defined 8-bit foreground color
// from 0 to 255 (38;5;n).
//
// 0- 7: standard colors (as in ESC [ 3037 m)
// 8- 15: high intensity colors (as in ESC [ 9097 m)
// 16-231: 6 × 6 × 6 cube (216 colors): 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5)
// 232-255: grayscale from black to white in 24 steps
//
Index(n uint8, arg interface{}) Value
// Gray from 0 to 23.
Gray(n uint8, arg interface{}) Value
//
// Background colors
//
//
// BgBlack background color (40)
BgBlack(arg interface{}) Value
// BgRed background color (41)
BgRed(arg interface{}) Value
// BgGreen background color (42)
BgGreen(arg interface{}) Value
// BgYellow background color (43)
BgYellow(arg interface{}) Value
// BgBrown background color (43)
//
// Deprecated: use BgYellow instead, following specification
BgBrown(arg interface{}) Value
// BgBlue background color (44)
BgBlue(arg interface{}) Value
// BgMagenta background color (45)
BgMagenta(arg interface{}) Value
// BgCyan background color (46)
BgCyan(arg interface{}) Value
// BgWhite background color (47)
BgWhite(arg interface{}) Value
//
// Bright background colors
//
// BgBrightBlack background color (100)
BgBrightBlack(arg interface{}) Value
// BgBrightRed background color (101)
BgBrightRed(arg interface{}) Value
// BgBrightGreen background color (102)
BgBrightGreen(arg interface{}) Value
// BgBrightYellow background color (103)
BgBrightYellow(arg interface{}) Value
// BgBrightBlue background color (104)
BgBrightBlue(arg interface{}) Value
// BgBrightMagenta background color (105)
BgBrightMagenta(arg interface{}) Value
// BgBrightCyan background color (106)
BgBrightCyan(arg interface{}) Value
// BgBrightWhite background color (107)
BgBrightWhite(arg interface{}) Value
//
// Other
//
// BgIndex of 8-bit pre-defined background color
// from 0 to 255 (48;5;n).
//
// 0- 7: standard colors (as in ESC [ 4047 m)
// 8- 15: high intensity colors (as in ESC [100107 m)
// 16-231: 6 × 6 × 6 cube (216 colors): 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5)
// 232-255: grayscale from black to white in 24 steps
//
BgIndex(n uint8, arg interface{}) Value
// BgGray from 0 to 23.
BgGray(n uint8, arg interface{}) Value
//
// Special
//
// Colorize removes existing colors and
// formats of the argument and applies given.
Colorize(arg interface{}, color Color) Value
//
// Support methods
//
// Sprintf allows to use colored format.
Sprintf(format interface{}, args ...interface{}) string
}
// NewAurora returns a new Aurora interface that
// will support or not support colors depending
// the enableColors argument
func NewAurora(enableColors bool) Aurora {
if enableColors {
return aurora{}
}
return auroraClear{}
}
// no colors
type auroraClear struct{}
func (auroraClear) Reset(arg interface{}) Value { return valueClear{arg} }
func (auroraClear) Bold(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) Faint(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) DoublyUnderline(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) Fraktur(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) Italic(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) Underline(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) SlowBlink(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) RapidBlink(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) Blink(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) Reverse(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) Inverse(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) Conceal(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) Hidden(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) CrossedOut(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) StrikeThrough(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) Framed(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) Encircled(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) Overlined(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) Black(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) Red(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) Green(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) Yellow(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) Brown(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) Blue(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) Magenta(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) Cyan(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) White(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) BrightBlack(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) BrightRed(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) BrightGreen(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) BrightYellow(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) BrightBlue(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) BrightMagenta(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) BrightCyan(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) BrightWhite(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) Index(_ uint8, arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) Gray(_ uint8, arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) BgBlack(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) BgRed(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) BgGreen(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) BgYellow(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) BgBrown(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) BgBlue(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) BgMagenta(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) BgCyan(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) BgWhite(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) BgBrightBlack(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) BgBrightRed(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) BgBrightGreen(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) BgBrightYellow(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) BgBrightBlue(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) BgBrightMagenta(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) BgBrightCyan(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) BgBrightWhite(arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) BgIndex(_ uint8, arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) BgGray(_ uint8, arg interface{}) Value {
return valueClear{arg}
}
func (auroraClear) Colorize(arg interface{}, _ Color) Value {
return valueClear{arg}
}
func (auroraClear) Sprintf(format interface{}, args ...interface{}) string {
if str, ok := format.(string); ok {
return fmt.Sprintf(str, args...)
}
return fmt.Sprintf(fmt.Sprint(format), args...)
}
// colorized
type aurora struct{}
func (aurora) Reset(arg interface{}) Value {
return Reset(arg)
}
func (aurora) Bold(arg interface{}) Value {
return Bold(arg)
}
func (aurora) Faint(arg interface{}) Value {
return Faint(arg)
}
func (aurora) DoublyUnderline(arg interface{}) Value {
return DoublyUnderline(arg)
}
func (aurora) Fraktur(arg interface{}) Value {
return Fraktur(arg)
}
func (aurora) Italic(arg interface{}) Value {
return Italic(arg)
}
func (aurora) Underline(arg interface{}) Value {
return Underline(arg)
}
func (aurora) SlowBlink(arg interface{}) Value {
return SlowBlink(arg)
}
func (aurora) RapidBlink(arg interface{}) Value {
return RapidBlink(arg)
}
func (aurora) Blink(arg interface{}) Value {
return Blink(arg)
}
func (aurora) Reverse(arg interface{}) Value {
return Reverse(arg)
}
func (aurora) Inverse(arg interface{}) Value {
return Inverse(arg)
}
func (aurora) Conceal(arg interface{}) Value {
return Conceal(arg)
}
func (aurora) Hidden(arg interface{}) Value {
return Hidden(arg)
}
func (aurora) CrossedOut(arg interface{}) Value {
return CrossedOut(arg)
}
func (aurora) StrikeThrough(arg interface{}) Value {
return StrikeThrough(arg)
}
func (aurora) Framed(arg interface{}) Value {
return Framed(arg)
}
func (aurora) Encircled(arg interface{}) Value {
return Encircled(arg)
}
func (aurora) Overlined(arg interface{}) Value {
return Overlined(arg)
}
func (aurora) Black(arg interface{}) Value {
return Black(arg)
}
func (aurora) Red(arg interface{}) Value {
return Red(arg)
}
func (aurora) Green(arg interface{}) Value {
return Green(arg)
}
func (aurora) Yellow(arg interface{}) Value {
return Yellow(arg)
}
func (aurora) Brown(arg interface{}) Value {
return Brown(arg)
}
func (aurora) Blue(arg interface{}) Value {
return Blue(arg)
}
func (aurora) Magenta(arg interface{}) Value {
return Magenta(arg)
}
func (aurora) Cyan(arg interface{}) Value {
return Cyan(arg)
}
func (aurora) White(arg interface{}) Value {
return White(arg)
}
func (aurora) BrightBlack(arg interface{}) Value {
return BrightBlack(arg)
}
func (aurora) BrightRed(arg interface{}) Value {
return BrightRed(arg)
}
func (aurora) BrightGreen(arg interface{}) Value {
return BrightGreen(arg)
}
func (aurora) BrightYellow(arg interface{}) Value {
return BrightYellow(arg)
}
func (aurora) BrightBlue(arg interface{}) Value {
return BrightBlue(arg)
}
func (aurora) BrightMagenta(arg interface{}) Value {
return BrightMagenta(arg)
}
func (aurora) BrightCyan(arg interface{}) Value {
return BrightCyan(arg)
}
func (aurora) BrightWhite(arg interface{}) Value {
return BrightWhite(arg)
}
func (aurora) Index(index uint8, arg interface{}) Value {
return Index(index, arg)
}
func (aurora) Gray(n uint8, arg interface{}) Value {
return Gray(n, arg)
}
func (aurora) BgBlack(arg interface{}) Value {
return BgBlack(arg)
}
func (aurora) BgRed(arg interface{}) Value {
return BgRed(arg)
}
func (aurora) BgGreen(arg interface{}) Value {
return BgGreen(arg)
}
func (aurora) BgYellow(arg interface{}) Value {
return BgYellow(arg)
}
func (aurora) BgBrown(arg interface{}) Value {
return BgBrown(arg)
}
func (aurora) BgBlue(arg interface{}) Value {
return BgBlue(arg)
}
func (aurora) BgMagenta(arg interface{}) Value {
return BgMagenta(arg)
}
func (aurora) BgCyan(arg interface{}) Value {
return BgCyan(arg)
}
func (aurora) BgWhite(arg interface{}) Value {
return BgWhite(arg)
}
func (aurora) BgBrightBlack(arg interface{}) Value {
return BgBrightBlack(arg)
}
func (aurora) BgBrightRed(arg interface{}) Value {
return BgBrightRed(arg)
}
func (aurora) BgBrightGreen(arg interface{}) Value {
return BgBrightGreen(arg)
}
func (aurora) BgBrightYellow(arg interface{}) Value {
return BgBrightYellow(arg)
}
func (aurora) BgBrightBlue(arg interface{}) Value {
return BgBrightBlue(arg)
}
func (aurora) BgBrightMagenta(arg interface{}) Value {
return BgBrightMagenta(arg)
}
func (aurora) BgBrightCyan(arg interface{}) Value {
return BgBrightCyan(arg)
}
func (aurora) BgBrightWhite(arg interface{}) Value {
return BgBrightWhite(arg)
}
func (aurora) BgIndex(n uint8, arg interface{}) Value {
return BgIndex(n, arg)
}
func (aurora) BgGray(n uint8, arg interface{}) Value {
return BgGray(n, arg)
}
func (aurora) Colorize(arg interface{}, color Color) Value {
return Colorize(arg, color)
}
func (aurora) Sprintf(format interface{}, args ...interface{}) string {
return Sprintf(format, args...)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
vendor/github.com/logrusorgru/aurora/aurora_formats.gif generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

398
vendor/github.com/logrusorgru/aurora/color.go generated vendored Normal file
View File

@ -0,0 +1,398 @@
//
// Copyright (c) 2016-2020 The Aurora Authors. All rights reserved.
// This program is free software. It comes without any warranty,
// to the extent permitted by applicable law. You can redistribute
// it and/or modify it under the terms of the Unlicense. See LICENSE
// file for more details or see below.
//
//
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <http://unlicense.org/>
//
package aurora
// A Color type is a color. It can contain
// one background color, one foreground color
// and a format, including ideogram related
// formats.
type Color uint
/*
Developer note.
The int type is architecture depended and can be
represented as int32 or int64.
Thus, we can use 32-bits only to be fast and
cross-platform.
All supported formats requires 14 bits. It is
first 14 bits.
A foreground color requires 8 bit + 1 bit (presence flag).
And the same for background color.
The Color representations
[ bg 8 bit ] [fg 8 bit ] [ fg/bg 2 bits ] [ fm 14 bits ]
https://play.golang.org/p/fq2zcNstFoF
*/
// Special formats
const (
BoldFm Color = 1 << iota // 1
FaintFm // 2
ItalicFm // 3
UnderlineFm // 4
SlowBlinkFm // 5
RapidBlinkFm // 6
ReverseFm // 7
ConcealFm // 8
CrossedOutFm // 9
FrakturFm // 20
DoublyUnderlineFm // 21 or bold off for some systems
FramedFm // 51
EncircledFm // 52
OverlinedFm // 53
InverseFm = ReverseFm // alias to ReverseFm
BlinkFm = SlowBlinkFm // alias to SlowBlinkFm
HiddenFm = ConcealFm // alias to ConcealFm
StrikeThroughFm = CrossedOutFm // alias to CrossedOutFm
maskFm = BoldFm | FaintFm |
ItalicFm | UnderlineFm |
SlowBlinkFm | RapidBlinkFm |
ReverseFm |
ConcealFm | CrossedOutFm |
FrakturFm | DoublyUnderlineFm |
FramedFm | EncircledFm | OverlinedFm
flagFg Color = 1 << 14 // presence flag (14th bit)
flagBg Color = 1 << 15 // presence flag (15th bit)
shiftFg = 16 // shift for foreground (starting from 16th bit)
shiftBg = 24 // shift for background (starting from 24th bit)
)
// Foreground colors and related formats
const (
// 8 bits
// [ 0; 7] - 30-37
// [ 8; 15] - 90-97
// [ 16; 231] - RGB
// [232; 255] - grayscale
BlackFg Color = (iota << shiftFg) | flagFg // 30, 90
RedFg // 31, 91
GreenFg // 32, 92
YellowFg // 33, 93
BlueFg // 34, 94
MagentaFg // 35, 95
CyanFg // 36, 96
WhiteFg // 37, 97
BrightFg Color = ((1 << 3) << shiftFg) | flagFg // -> 90
// the BrightFg itself doesn't represent
// a color, thus it has not flagFg
// 5 bits
// BrownFg represents brown foreground color.
//
// Deprecated: use YellowFg instead, following specifications
BrownFg = YellowFg
//
maskFg = (0xff << shiftFg) | flagFg
)
// Background colors and related formats
const (
// 8 bits
// [ 0; 7] - 40-47
// [ 8; 15] - 100-107
// [ 16; 231] - RGB
// [232; 255] - grayscale
BlackBg Color = (iota << shiftBg) | flagBg // 40, 100
RedBg // 41, 101
GreenBg // 42, 102
YellowBg // 43, 103
BlueBg // 44, 104
MagentaBg // 45, 105
CyanBg // 46, 106
WhiteBg // 47, 107
BrightBg Color = ((1 << 3) << shiftBg) | flagBg // -> 100
// the BrightBg itself doesn't represent
// a color, thus it has not flagBg
// 5 bits
// BrownBg represents brown foreground color.
//
// Deprecated: use YellowBg instead, following specifications
BrownBg = YellowBg
//
maskBg = (0xff << shiftBg) | flagBg
)
const (
availFlags = "-+# 0"
esc = "\033["
clear = esc + "0m"
)
// IsValid returns true always
//
// Deprecated: don't use this method anymore
func (c Color) IsValid() bool {
return true
}
// Nos returns string like 1;7;31;45. It
// may be an empty string for empty color.
// If the zero is true, then the string
// is prepended with 0;
func (c Color) Nos(zero bool) string {
return string(c.appendNos(make([]byte, 0, 59), zero))
}
func appendCond(bs []byte, cond, semi bool, vals ...byte) []byte {
if !cond {
return bs
}
return appendSemi(bs, semi, vals...)
}
// if the semi is true, then prepend with semicolon
func appendSemi(bs []byte, semi bool, vals ...byte) []byte {
if semi {
bs = append(bs, ';')
}
return append(bs, vals...)
}
func itoa(t byte) string {
var (
a [3]byte
j = 2
)
for i := 0; i < 3; i, j = i+1, j-1 {
a[j] = '0' + t%10
if t = t / 10; t == 0 {
break
}
}
return string(a[j:])
}
func (c Color) appendFg(bs []byte, zero bool) []byte {
if zero || c&maskFm != 0 {
bs = append(bs, ';')
}
// 0- 7 : 30-37
// 8-15 : 90-97
// > 15 : 38;5;val
switch fg := (c & maskFg) >> shiftFg; {
case fg <= 7:
// '3' and the value itself
bs = append(bs, '3', '0'+byte(fg))
case fg <= 15:
// '9' and the value itself
bs = append(bs, '9', '0'+byte(fg&^0x08)) // clear bright flag
default:
bs = append(bs, '3', '8', ';', '5', ';')
bs = append(bs, itoa(byte(fg))...)
}
return bs
}
func (c Color) appendBg(bs []byte, zero bool) []byte {
if zero || c&(maskFm|maskFg) != 0 {
bs = append(bs, ';')
}
// 0- 7 : 40- 47
// 8-15 : 100-107
// > 15 : 48;5;val
switch fg := (c & maskBg) >> shiftBg; {
case fg <= 7:
// '3' and the value itself
bs = append(bs, '4', '0'+byte(fg))
case fg <= 15:
// '1', '0' and the value itself
bs = append(bs, '1', '0', '0'+byte(fg&^0x08)) // clear bright flag
default:
bs = append(bs, '4', '8', ';', '5', ';')
bs = append(bs, itoa(byte(fg))...)
}
return bs
}
func (c Color) appendFm9(bs []byte, zero bool) []byte {
bs = appendCond(bs, c&ItalicFm != 0,
zero || c&(BoldFm|FaintFm) != 0,
'3')
bs = appendCond(bs, c&UnderlineFm != 0,
zero || c&(BoldFm|FaintFm|ItalicFm) != 0,
'4')
// don't combine slow and rapid blink using only
// on of them, preferring slow blink
if c&SlowBlinkFm != 0 {
bs = appendSemi(bs,
zero || c&(BoldFm|FaintFm|ItalicFm|UnderlineFm) != 0,
'5')
} else if c&RapidBlinkFm != 0 {
bs = appendSemi(bs,
zero || c&(BoldFm|FaintFm|ItalicFm|UnderlineFm) != 0,
'6')
}
// including 1-2
const mask6i = BoldFm | FaintFm |
ItalicFm | UnderlineFm |
SlowBlinkFm | RapidBlinkFm
bs = appendCond(bs, c&ReverseFm != 0,
zero || c&(mask6i) != 0,
'7')
bs = appendCond(bs, c&ConcealFm != 0,
zero || c&(mask6i|ReverseFm) != 0,
'8')
bs = appendCond(bs, c&CrossedOutFm != 0,
zero || c&(mask6i|ReverseFm|ConcealFm) != 0,
'9')
return bs
}
// append 1;3;38;5;216 like string that represents ANSI
// color of the Color; the zero argument requires
// appending of '0' before to reset previous format
// and colors
func (c Color) appendNos(bs []byte, zero bool) []byte {
if zero {
bs = append(bs, '0') // reset previous
}
// formats
//
if c&maskFm != 0 {
// 1-2
// don't combine bold and faint using only on of them, preferring bold
if c&BoldFm != 0 {
bs = appendSemi(bs, zero, '1')
} else if c&FaintFm != 0 {
bs = appendSemi(bs, zero, '2')
}
// 3-9
const mask9 = ItalicFm | UnderlineFm |
SlowBlinkFm | RapidBlinkFm |
ReverseFm | ConcealFm | CrossedOutFm
if c&mask9 != 0 {
bs = c.appendFm9(bs, zero)
}
// 20-21
const (
mask21 = FrakturFm | DoublyUnderlineFm
mask9i = BoldFm | FaintFm | mask9
)
if c&mask21 != 0 {
bs = appendCond(bs, c&FrakturFm != 0,
zero || c&mask9i != 0,
'2', '0')
bs = appendCond(bs, c&DoublyUnderlineFm != 0,
zero || c&(mask9i|FrakturFm) != 0,
'2', '1')
}
// 50-53
const (
mask53 = FramedFm | EncircledFm | OverlinedFm
mask21i = mask9i | mask21
)
if c&mask53 != 0 {
bs = appendCond(bs, c&FramedFm != 0,
zero || c&mask21i != 0,
'5', '1')
bs = appendCond(bs, c&EncircledFm != 0,
zero || c&(mask21i|FramedFm) != 0,
'5', '2')
bs = appendCond(bs, c&OverlinedFm != 0,
zero || c&(mask21i|FramedFm|EncircledFm) != 0,
'5', '3')
}
}
// foreground
if c&maskFg != 0 {
bs = c.appendFg(bs, zero)
}
// background
if c&maskBg != 0 {
bs = c.appendBg(bs, zero)
}
return bs
}

BIN
vendor/github.com/logrusorgru/aurora/disable.png generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 B

BIN
vendor/github.com/logrusorgru/aurora/enable.png generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

BIN
vendor/github.com/logrusorgru/aurora/gopher_aurora.png generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
vendor/github.com/logrusorgru/aurora/printf.png generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
vendor/github.com/logrusorgru/aurora/simple.png generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

68
vendor/github.com/logrusorgru/aurora/sprintf.go generated vendored Normal file
View File

@ -0,0 +1,68 @@
//
// Copyright (c) 2016-2020 The Aurora Authors. All rights reserved.
// This program is free software. It comes without any warranty,
// to the extent permitted by applicable law. You can redistribute
// it and/or modify it under the terms of the Unlicense. See LICENSE
// file for more details or see below.
//
//
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <http://unlicense.org/>
//
package aurora
import (
"fmt"
)
// Sprintf allows to use Value as format. For example
//
// v := Sprintf(Red("total: +3.5f points"), Blue(3.14))
//
// In this case "total:" and "points" will be red, but
// 3.14 will be blue. But, in another example
//
// v := Sprintf(Red("total: +3.5f points"), 3.14)
//
// full string will be red. And no way to clear 3.14 to
// default format and color
func Sprintf(format interface{}, args ...interface{}) string {
switch ft := format.(type) {
case string:
return fmt.Sprintf(ft, args...)
case Value:
for i, v := range args {
if val, ok := v.(Value); ok {
args[i] = val.setTail(ft.Color())
continue
}
}
return fmt.Sprintf(ft.String(), args...)
}
// unknown type of format (we hope it's a string)
return fmt.Sprintf(fmt.Sprint(format), args...)
}

BIN
vendor/github.com/logrusorgru/aurora/sprintf.png generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

745
vendor/github.com/logrusorgru/aurora/value.go generated vendored Normal file
View File

@ -0,0 +1,745 @@
//
// Copyright (c) 2016-2020 The Aurora Authors. All rights reserved.
// This program is free software. It comes without any warranty,
// to the extent permitted by applicable law. You can redistribute
// it and/or modify it under the terms of the Unlicense. See LICENSE
// file for more details or see below.
//
//
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <http://unlicense.org/>
//
package aurora
import (
"fmt"
"strconv"
"unicode/utf8"
)
// A Value represents any printable value
// with it's color
type Value interface {
// String returns string with colors. If there are any color
// or format the string will be terminated with \033[0m
fmt.Stringer
// Format implements fmt.Formatter interface
fmt.Formatter
// Color returns value's color
Color() Color
// Value returns value's value (welcome to the tautology club)
Value() interface{}
// internals
tail() Color
setTail(Color) Value
// Bleach returns copy of original value without colors
//
// Deprecated: use Reset instead.
Bleach() Value
// Reset colors and formats
Reset() Value
//
// Formats
//
//
// Bold or increased intensity (1).
Bold() Value
// Faint, decreased intensity, reset the Bold (2).
Faint() Value
//
// DoublyUnderline or Bold off, double-underline
// per ECMA-48 (21). It depends.
DoublyUnderline() Value
// Fraktur, rarely supported (20).
Fraktur() Value
//
// Italic, not widely supported, sometimes
// treated as inverse (3).
Italic() Value
// Underline (4).
Underline() Value
//
// SlowBlink, blinking less than 150
// per minute (5).
SlowBlink() Value
// RapidBlink, blinking 150+ per minute,
// not widely supported (6).
RapidBlink() Value
// Blink is alias for the SlowBlink.
Blink() Value
//
// Reverse video, swap foreground and
// background colors (7).
Reverse() Value
// Inverse is alias for the Reverse
Inverse() Value
//
// Conceal, hidden, not widely supported (8).
Conceal() Value
// Hidden is alias for the Conceal
Hidden() Value
//
// CrossedOut, characters legible, but
// marked for deletion (9).
CrossedOut() Value
// StrikeThrough is alias for the CrossedOut.
StrikeThrough() Value
//
// Framed (51).
Framed() Value
// Encircled (52).
Encircled() Value
//
// Overlined (53).
Overlined() Value
//
// Foreground colors
//
//
// Black foreground color (30)
Black() Value
// Red foreground color (31)
Red() Value
// Green foreground color (32)
Green() Value
// Yellow foreground color (33)
Yellow() Value
// Brown foreground color (33)
//
// Deprecated: use Yellow instead, following specification
Brown() Value
// Blue foreground color (34)
Blue() Value
// Magenta foreground color (35)
Magenta() Value
// Cyan foreground color (36)
Cyan() Value
// White foreground color (37)
White() Value
//
// Bright foreground colors
//
// BrightBlack foreground color (90)
BrightBlack() Value
// BrightRed foreground color (91)
BrightRed() Value
// BrightGreen foreground color (92)
BrightGreen() Value
// BrightYellow foreground color (93)
BrightYellow() Value
// BrightBlue foreground color (94)
BrightBlue() Value
// BrightMagenta foreground color (95)
BrightMagenta() Value
// BrightCyan foreground color (96)
BrightCyan() Value
// BrightWhite foreground color (97)
BrightWhite() Value
//
// Other
//
// Index of pre-defined 8-bit foreground color
// from 0 to 255 (38;5;n).
//
// 0- 7: standard colors (as in ESC [ 3037 m)
// 8- 15: high intensity colors (as in ESC [ 9097 m)
// 16-231: 6 × 6 × 6 cube (216 colors): 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5)
// 232-255: grayscale from black to white in 24 steps
//
Index(n uint8) Value
// Gray from 0 to 24.
Gray(n uint8) Value
//
// Background colors
//
//
// BgBlack background color (40)
BgBlack() Value
// BgRed background color (41)
BgRed() Value
// BgGreen background color (42)
BgGreen() Value
// BgYellow background color (43)
BgYellow() Value
// BgBrown background color (43)
//
// Deprecated: use BgYellow instead, following specification
BgBrown() Value
// BgBlue background color (44)
BgBlue() Value
// BgMagenta background color (45)
BgMagenta() Value
// BgCyan background color (46)
BgCyan() Value
// BgWhite background color (47)
BgWhite() Value
//
// Bright background colors
//
// BgBrightBlack background color (100)
BgBrightBlack() Value
// BgBrightRed background color (101)
BgBrightRed() Value
// BgBrightGreen background color (102)
BgBrightGreen() Value
// BgBrightYellow background color (103)
BgBrightYellow() Value
// BgBrightBlue background color (104)
BgBrightBlue() Value
// BgBrightMagenta background color (105)
BgBrightMagenta() Value
// BgBrightCyan background color (106)
BgBrightCyan() Value
// BgBrightWhite background color (107)
BgBrightWhite() Value
//
// Other
//
// BgIndex of 8-bit pre-defined background color
// from 0 to 255 (48;5;n).
//
// 0- 7: standard colors (as in ESC [ 4047 m)
// 8- 15: high intensity colors (as in ESC [100107 m)
// 16-231: 6 × 6 × 6 cube (216 colors): 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5)
// 232-255: grayscale from black to white in 24 steps
//
BgIndex(n uint8) Value
// BgGray from 0 to 24.
BgGray(n uint8) Value
//
// Special
//
// Colorize removes existing colors and
// formats of the argument and applies given.
Colorize(color Color) Value
}
// Value without colors
type valueClear struct {
value interface{}
}
func (vc valueClear) String() string { return fmt.Sprint(vc.value) }
func (vc valueClear) Color() Color { return 0 }
func (vc valueClear) Value() interface{} { return vc.value }
func (vc valueClear) tail() Color { return 0 }
func (vc valueClear) setTail(Color) Value { return vc }
func (vc valueClear) Bleach() Value { return vc }
func (vc valueClear) Reset() Value { return vc }
func (vc valueClear) Bold() Value { return vc }
func (vc valueClear) Faint() Value { return vc }
func (vc valueClear) DoublyUnderline() Value { return vc }
func (vc valueClear) Fraktur() Value { return vc }
func (vc valueClear) Italic() Value { return vc }
func (vc valueClear) Underline() Value { return vc }
func (vc valueClear) SlowBlink() Value { return vc }
func (vc valueClear) RapidBlink() Value { return vc }
func (vc valueClear) Blink() Value { return vc }
func (vc valueClear) Reverse() Value { return vc }
func (vc valueClear) Inverse() Value { return vc }
func (vc valueClear) Conceal() Value { return vc }
func (vc valueClear) Hidden() Value { return vc }
func (vc valueClear) CrossedOut() Value { return vc }
func (vc valueClear) StrikeThrough() Value { return vc }
func (vc valueClear) Framed() Value { return vc }
func (vc valueClear) Encircled() Value { return vc }
func (vc valueClear) Overlined() Value { return vc }
func (vc valueClear) Black() Value { return vc }
func (vc valueClear) Red() Value { return vc }
func (vc valueClear) Green() Value { return vc }
func (vc valueClear) Yellow() Value { return vc }
func (vc valueClear) Brown() Value { return vc }
func (vc valueClear) Blue() Value { return vc }
func (vc valueClear) Magenta() Value { return vc }
func (vc valueClear) Cyan() Value { return vc }
func (vc valueClear) White() Value { return vc }
func (vc valueClear) BrightBlack() Value { return vc }
func (vc valueClear) BrightRed() Value { return vc }
func (vc valueClear) BrightGreen() Value { return vc }
func (vc valueClear) BrightYellow() Value { return vc }
func (vc valueClear) BrightBlue() Value { return vc }
func (vc valueClear) BrightMagenta() Value { return vc }
func (vc valueClear) BrightCyan() Value { return vc }
func (vc valueClear) BrightWhite() Value { return vc }
func (vc valueClear) Index(uint8) Value { return vc }
func (vc valueClear) Gray(uint8) Value { return vc }
func (vc valueClear) BgBlack() Value { return vc }
func (vc valueClear) BgRed() Value { return vc }
func (vc valueClear) BgGreen() Value { return vc }
func (vc valueClear) BgYellow() Value { return vc }
func (vc valueClear) BgBrown() Value { return vc }
func (vc valueClear) BgBlue() Value { return vc }
func (vc valueClear) BgMagenta() Value { return vc }
func (vc valueClear) BgCyan() Value { return vc }
func (vc valueClear) BgWhite() Value { return vc }
func (vc valueClear) BgBrightBlack() Value { return vc }
func (vc valueClear) BgBrightRed() Value { return vc }
func (vc valueClear) BgBrightGreen() Value { return vc }
func (vc valueClear) BgBrightYellow() Value { return vc }
func (vc valueClear) BgBrightBlue() Value { return vc }
func (vc valueClear) BgBrightMagenta() Value { return vc }
func (vc valueClear) BgBrightCyan() Value { return vc }
func (vc valueClear) BgBrightWhite() Value { return vc }
func (vc valueClear) BgIndex(uint8) Value { return vc }
func (vc valueClear) BgGray(uint8) Value { return vc }
func (vc valueClear) Colorize(Color) Value { return vc }
func (vc valueClear) Format(s fmt.State, verb rune) {
// it's enough for many cases (%-+020.10f)
// % - 1
// availFlags - 3 (5)
// width - 2
// prec - 3 (.23)
// verb - 1
// --------------
// 10
format := make([]byte, 1, 10)
format[0] = '%'
var f byte
for i := 0; i < len(availFlags); i++ {
if f = availFlags[i]; s.Flag(int(f)) {
format = append(format, f)
}
}
var width, prec int
var ok bool
if width, ok = s.Width(); ok {
format = strconv.AppendInt(format, int64(width), 10)
}
if prec, ok = s.Precision(); ok {
format = append(format, '.')
format = strconv.AppendInt(format, int64(prec), 10)
}
if verb > utf8.RuneSelf {
format = append(format, string(verb)...)
} else {
format = append(format, byte(verb))
}
fmt.Fprintf(s, string(format), vc.value)
}
// Value within colors
type value struct {
value interface{} // value as it
color Color // this color
tailColor Color // tail color
}
func (v value) String() string {
if v.color != 0 {
if v.tailColor != 0 {
return esc + v.color.Nos(true) + "m" +
fmt.Sprint(v.value) +
esc + v.tailColor.Nos(true) + "m"
}
return esc + v.color.Nos(false) + "m" + fmt.Sprint(v.value) + clear
}
return fmt.Sprint(v.value)
}
func (v value) Color() Color { return v.color }
func (v value) Bleach() Value {
v.color, v.tailColor = 0, 0
return v
}
func (v value) Reset() Value {
v.color, v.tailColor = 0, 0
return v
}
func (v value) tail() Color { return v.tailColor }
func (v value) setTail(t Color) Value {
v.tailColor = t
return v
}
func (v value) Value() interface{} { return v.value }
func (v value) Format(s fmt.State, verb rune) {
// it's enough for many cases (%-+020.10f)
// % - 1
// availFlags - 3 (5)
// width - 2
// prec - 3 (.23)
// verb - 1
// --------------
// 10
// +
// \033[ 5
// 0;1;3;4;5;7;8;9;20;21;51;52;53 30
// 38;5;216 8
// 48;5;216 8
// m 1
// +
// \033[0m 7
//
// x2 (possible tail color)
//
// 10 + 59 * 2 = 128
format := make([]byte, 0, 128)
if v.color != 0 {
format = append(format, esc...)
format = v.color.appendNos(format, v.tailColor != 0)
format = append(format, 'm')
}
format = append(format, '%')
var f byte
for i := 0; i < len(availFlags); i++ {
if f = availFlags[i]; s.Flag(int(f)) {
format = append(format, f)
}
}
var width, prec int
var ok bool
if width, ok = s.Width(); ok {
format = strconv.AppendInt(format, int64(width), 10)
}
if prec, ok = s.Precision(); ok {
format = append(format, '.')
format = strconv.AppendInt(format, int64(prec), 10)
}
if verb > utf8.RuneSelf {
format = append(format, string(verb)...)
} else {
format = append(format, byte(verb))
}
if v.color != 0 {
if v.tailColor != 0 {
// set next (previous) format clearing current one
format = append(format, esc...)
format = v.tailColor.appendNos(format, true)
format = append(format, 'm')
} else {
format = append(format, clear...) // just clear
}
}
fmt.Fprintf(s, string(format), v.value)
}
func (v value) Bold() Value {
v.color = (v.color &^ FaintFm) | BoldFm
return v
}
func (v value) Faint() Value {
v.color = (v.color &^ BoldFm) | FaintFm
return v
}
func (v value) DoublyUnderline() Value {
v.color |= DoublyUnderlineFm
return v
}
func (v value) Fraktur() Value {
v.color |= FrakturFm
return v
}
func (v value) Italic() Value {
v.color |= ItalicFm
return v
}
func (v value) Underline() Value {
v.color |= UnderlineFm
return v
}
func (v value) SlowBlink() Value {
v.color = (v.color &^ RapidBlinkFm) | SlowBlinkFm
return v
}
func (v value) RapidBlink() Value {
v.color = (v.color &^ SlowBlinkFm) | RapidBlinkFm
return v
}
func (v value) Blink() Value {
return v.SlowBlink()
}
func (v value) Reverse() Value {
v.color |= ReverseFm
return v
}
func (v value) Inverse() Value {
return v.Reverse()
}
func (v value) Conceal() Value {
v.color |= ConcealFm
return v
}
func (v value) Hidden() Value {
return v.Conceal()
}
func (v value) CrossedOut() Value {
v.color |= CrossedOutFm
return v
}
func (v value) StrikeThrough() Value {
return v.CrossedOut()
}
func (v value) Framed() Value {
v.color |= FramedFm
return v
}
func (v value) Encircled() Value {
v.color |= EncircledFm
return v
}
func (v value) Overlined() Value {
v.color |= OverlinedFm
return v
}
func (v value) Black() Value {
v.color = (v.color &^ maskFg) | BlackFg
return v
}
func (v value) Red() Value {
v.color = (v.color &^ maskFg) | RedFg
return v
}
func (v value) Green() Value {
v.color = (v.color &^ maskFg) | GreenFg
return v
}
func (v value) Yellow() Value {
v.color = (v.color &^ maskFg) | YellowFg
return v
}
func (v value) Brown() Value {
return v.Yellow()
}
func (v value) Blue() Value {
v.color = (v.color &^ maskFg) | BlueFg
return v
}
func (v value) Magenta() Value {
v.color = (v.color &^ maskFg) | MagentaFg
return v
}
func (v value) Cyan() Value {
v.color = (v.color &^ maskFg) | CyanFg
return v
}
func (v value) White() Value {
v.color = (v.color &^ maskFg) | WhiteFg
return v
}
func (v value) BrightBlack() Value {
v.color = (v.color &^ maskFg) | BrightFg | BlackFg
return v
}
func (v value) BrightRed() Value {
v.color = (v.color &^ maskFg) | BrightFg | RedFg
return v
}
func (v value) BrightGreen() Value {
v.color = (v.color &^ maskFg) | BrightFg | GreenFg
return v
}
func (v value) BrightYellow() Value {
v.color = (v.color &^ maskFg) | BrightFg | YellowFg
return v
}
func (v value) BrightBlue() Value {
v.color = (v.color &^ maskFg) | BrightFg | BlueFg
return v
}
func (v value) BrightMagenta() Value {
v.color = (v.color &^ maskFg) | BrightFg | MagentaFg
return v
}
func (v value) BrightCyan() Value {
v.color = (v.color &^ maskFg) | BrightFg | CyanFg
return v
}
func (v value) BrightWhite() Value {
v.color = (v.color &^ maskFg) | BrightFg | WhiteFg
return v
}
func (v value) Index(n uint8) Value {
v.color = (v.color &^ maskFg) | (Color(n) << shiftFg) | flagFg
return v
}
func (v value) Gray(n uint8) Value {
if n > 23 {
n = 23
}
v.color = (v.color &^ maskFg) | (Color(232+n) << shiftFg) | flagFg
return v
}
func (v value) BgBlack() Value {
v.color = (v.color &^ maskBg) | BlackBg
return v
}
func (v value) BgRed() Value {
v.color = (v.color &^ maskBg) | RedBg
return v
}
func (v value) BgGreen() Value {
v.color = (v.color &^ maskBg) | GreenBg
return v
}
func (v value) BgYellow() Value {
v.color = (v.color &^ maskBg) | YellowBg
return v
}
func (v value) BgBrown() Value {
return v.BgYellow()
}
func (v value) BgBlue() Value {
v.color = (v.color &^ maskBg) | BlueBg
return v
}
func (v value) BgMagenta() Value {
v.color = (v.color &^ maskBg) | MagentaBg
return v
}
func (v value) BgCyan() Value {
v.color = (v.color &^ maskBg) | CyanBg
return v
}
func (v value) BgWhite() Value {
v.color = (v.color &^ maskBg) | WhiteBg
return v
}
func (v value) BgBrightBlack() Value {
v.color = (v.color &^ maskBg) | BrightBg | BlackBg
return v
}
func (v value) BgBrightRed() Value {
v.color = (v.color &^ maskBg) | BrightBg | RedBg
return v
}
func (v value) BgBrightGreen() Value {
v.color = (v.color &^ maskBg) | BrightBg | GreenBg
return v
}
func (v value) BgBrightYellow() Value {
v.color = (v.color &^ maskBg) | BrightBg | YellowBg
return v
}
func (v value) BgBrightBlue() Value {
v.color = (v.color &^ maskBg) | BrightBg | BlueBg
return v
}
func (v value) BgBrightMagenta() Value {
v.color = (v.color &^ maskBg) | BrightBg | MagentaBg
return v
}
func (v value) BgBrightCyan() Value {
v.color = (v.color &^ maskBg) | BrightBg | CyanBg
return v
}
func (v value) BgBrightWhite() Value {
v.color = (v.color &^ maskBg) | BrightBg | WhiteBg
return v
}
func (v value) BgIndex(n uint8) Value {
v.color = (v.color &^ maskBg) | (Color(n) << shiftBg) | flagBg
return v
}
func (v value) BgGray(n uint8) Value {
if n > 23 {
n = 23
}
v.color = (v.color &^ maskBg) | (Color(232+n) << shiftBg) | flagBg
return v
}
func (v value) Colorize(color Color) Value {
v.color = color
return v
}

558
vendor/github.com/logrusorgru/aurora/wrap.go generated vendored Normal file
View File

@ -0,0 +1,558 @@
//
// Copyright (c) 2016-2020 The Aurora Authors. All rights reserved.
// This program is free software. It comes without any warranty,
// to the extent permitted by applicable law. You can redistribute
// it and/or modify it under the terms of the Unlicense. See LICENSE
// file for more details or see below.
//
//
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <http://unlicense.org/>
//
package aurora
// Colorize wraps given value into Value with
// given colors. For example
//
// s := Colorize("some", BlueFg|GreenBg|BoldFm)
//
// returns a Value with blue foreground, green
// background and bold. Unlike functions like
// Red/BgBlue/Bold etc. This function clears
// all previous colors and formats. Thus
//
// s := Colorize(Red("some"), BgBlue)
//
// clears red color from value
func Colorize(arg interface{}, color Color) Value {
if val, ok := arg.(value); ok {
val.color = color
return val
}
return value{arg, color, 0}
}
// Reset wraps given argument returning Value
// without formats and colors.
func Reset(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.Reset()
}
return value{value: arg}
}
//
// Formats
//
// Bold or increased intensity (1).
func Bold(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.Bold()
}
return value{value: arg, color: BoldFm}
}
// Faint decreases intensity (2).
// The Faint rejects the Bold.
func Faint(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.Faint()
}
return value{value: arg, color: FaintFm}
}
// DoublyUnderline or Bold off, double-underline
// per ECMA-48 (21).
func DoublyUnderline(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.DoublyUnderline()
}
return value{value: arg, color: DoublyUnderlineFm}
}
// Fraktur is rarely supported (20).
func Fraktur(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.Fraktur()
}
return value{value: arg, color: FrakturFm}
}
// Italic is not widely supported, sometimes
// treated as inverse (3).
func Italic(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.Italic()
}
return value{value: arg, color: ItalicFm}
}
// Underline (4).
func Underline(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.Underline()
}
return value{value: arg, color: UnderlineFm}
}
// SlowBlink makes text blink less than
// 150 per minute (5).
func SlowBlink(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.SlowBlink()
}
return value{value: arg, color: SlowBlinkFm}
}
// RapidBlink makes text blink 150+ per
// minute. It is not widely supported (6).
func RapidBlink(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.RapidBlink()
}
return value{value: arg, color: RapidBlinkFm}
}
// Blink is alias for the SlowBlink.
func Blink(arg interface{}) Value {
return SlowBlink(arg)
}
// Reverse video, swap foreground and
// background colors (7).
func Reverse(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.Reverse()
}
return value{value: arg, color: ReverseFm}
}
// Inverse is alias for the Reverse
func Inverse(arg interface{}) Value {
return Reverse(arg)
}
// Conceal hides text, preserving an ability to select
// the text and copy it. It is not widely supported (8).
func Conceal(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.Conceal()
}
return value{value: arg, color: ConcealFm}
}
// Hidden is alias for the Conceal
func Hidden(arg interface{}) Value {
return Conceal(arg)
}
// CrossedOut makes characters legible, but
// marked for deletion (9).
func CrossedOut(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.CrossedOut()
}
return value{value: arg, color: CrossedOutFm}
}
// StrikeThrough is alias for the CrossedOut.
func StrikeThrough(arg interface{}) Value {
return CrossedOut(arg)
}
// Framed (51).
func Framed(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.Framed()
}
return value{value: arg, color: FramedFm}
}
// Encircled (52).
func Encircled(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.Encircled()
}
return value{value: arg, color: EncircledFm}
}
// Overlined (53).
func Overlined(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.Overlined()
}
return value{value: arg, color: OverlinedFm}
}
//
// Foreground colors
//
//
// Black foreground color (30)
func Black(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.Black()
}
return value{value: arg, color: BlackFg}
}
// Red foreground color (31)
func Red(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.Red()
}
return value{value: arg, color: RedFg}
}
// Green foreground color (32)
func Green(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.Green()
}
return value{value: arg, color: GreenFg}
}
// Yellow foreground color (33)
func Yellow(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.Yellow()
}
return value{value: arg, color: YellowFg}
}
// Brown foreground color (33)
//
// Deprecated: use Yellow instead, following specification
func Brown(arg interface{}) Value {
return Yellow(arg)
}
// Blue foreground color (34)
func Blue(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.Blue()
}
return value{value: arg, color: BlueFg}
}
// Magenta foreground color (35)
func Magenta(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.Magenta()
}
return value{value: arg, color: MagentaFg}
}
// Cyan foreground color (36)
func Cyan(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.Cyan()
}
return value{value: arg, color: CyanFg}
}
// White foreground color (37)
func White(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.White()
}
return value{value: arg, color: WhiteFg}
}
//
// Bright foreground colors
//
// BrightBlack foreground color (90)
func BrightBlack(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.BrightBlack()
}
return value{value: arg, color: BrightFg | BlackFg}
}
// BrightRed foreground color (91)
func BrightRed(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.BrightRed()
}
return value{value: arg, color: BrightFg | RedFg}
}
// BrightGreen foreground color (92)
func BrightGreen(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.BrightGreen()
}
return value{value: arg, color: BrightFg | GreenFg}
}
// BrightYellow foreground color (93)
func BrightYellow(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.BrightYellow()
}
return value{value: arg, color: BrightFg | YellowFg}
}
// BrightBlue foreground color (94)
func BrightBlue(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.BrightBlue()
}
return value{value: arg, color: BrightFg | BlueFg}
}
// BrightMagenta foreground color (95)
func BrightMagenta(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.BrightMagenta()
}
return value{value: arg, color: BrightFg | MagentaFg}
}
// BrightCyan foreground color (96)
func BrightCyan(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.BrightCyan()
}
return value{value: arg, color: BrightFg | CyanFg}
}
// BrightWhite foreground color (97)
func BrightWhite(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.BrightWhite()
}
return value{value: arg, color: BrightFg | WhiteFg}
}
//
// Other
//
// Index of pre-defined 8-bit foreground color
// from 0 to 255 (38;5;n).
//
// 0- 7: standard colors (as in ESC [ 3037 m)
// 8- 15: high intensity colors (as in ESC [ 9097 m)
// 16-231: 6 × 6 × 6 cube (216 colors): 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5)
// 232-255: grayscale from black to white in 24 steps
//
func Index(n uint8, arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.Index(n)
}
return value{value: arg, color: (Color(n) << shiftFg) | flagFg}
}
// Gray from 0 to 24.
func Gray(n uint8, arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.Gray(n)
}
if n > 23 {
n = 23
}
return value{value: arg, color: (Color(232+n) << shiftFg) | flagFg}
}
//
// Background colors
//
//
// BgBlack background color (40)
func BgBlack(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.BgBlack()
}
return value{value: arg, color: BlackBg}
}
// BgRed background color (41)
func BgRed(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.BgRed()
}
return value{value: arg, color: RedBg}
}
// BgGreen background color (42)
func BgGreen(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.BgGreen()
}
return value{value: arg, color: GreenBg}
}
// BgYellow background color (43)
func BgYellow(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.BgYellow()
}
return value{value: arg, color: YellowBg}
}
// BgBrown background color (43)
//
// Deprecated: use BgYellow instead, following specification
func BgBrown(arg interface{}) Value {
return BgYellow(arg)
}
// BgBlue background color (44)
func BgBlue(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.BgBlue()
}
return value{value: arg, color: BlueBg}
}
// BgMagenta background color (45)
func BgMagenta(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.BgMagenta()
}
return value{value: arg, color: MagentaBg}
}
// BgCyan background color (46)
func BgCyan(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.BgCyan()
}
return value{value: arg, color: CyanBg}
}
// BgWhite background color (47)
func BgWhite(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.BgWhite()
}
return value{value: arg, color: WhiteBg}
}
//
// Bright background colors
//
// BgBrightBlack background color (100)
func BgBrightBlack(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.BgBrightBlack()
}
return value{value: arg, color: BrightBg | BlackBg}
}
// BgBrightRed background color (101)
func BgBrightRed(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.BgBrightRed()
}
return value{value: arg, color: BrightBg | RedBg}
}
// BgBrightGreen background color (102)
func BgBrightGreen(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.BgBrightGreen()
}
return value{value: arg, color: BrightBg | GreenBg}
}
// BgBrightYellow background color (103)
func BgBrightYellow(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.BgBrightYellow()
}
return value{value: arg, color: BrightBg | YellowBg}
}
// BgBrightBlue background color (104)
func BgBrightBlue(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.BgBrightBlue()
}
return value{value: arg, color: BrightBg | BlueBg}
}
// BgBrightMagenta background color (105)
func BgBrightMagenta(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.BgBrightMagenta()
}
return value{value: arg, color: BrightBg | MagentaBg}
}
// BgBrightCyan background color (106)
func BgBrightCyan(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.BgBrightCyan()
}
return value{value: arg, color: BrightBg | CyanBg}
}
// BgBrightWhite background color (107)
func BgBrightWhite(arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.BgBrightWhite()
}
return value{value: arg, color: BrightBg | WhiteBg}
}
//
// Other
//
// BgIndex of 8-bit pre-defined background color
// from 0 to 255 (48;5;n).
//
// 0- 7: standard colors (as in ESC [ 4047 m)
// 8- 15: high intensity colors (as in ESC [100107 m)
// 16-231: 6 × 6 × 6 cube (216 colors): 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5)
// 232-255: grayscale from black to white in 24 steps
//
func BgIndex(n uint8, arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.BgIndex(n)
}
return value{value: arg, color: (Color(n) << shiftBg) | flagBg}
}
// BgGray from 0 to 24.
func BgGray(n uint8, arg interface{}) Value {
if val, ok := arg.(Value); ok {
return val.BgGray(n)
}
if n > 23 {
n = 23
}
return value{value: arg, color: (Color(n+232) << shiftBg) | flagBg}
}

View File

@ -1,54 +1,21 @@
Microsoft Reference Source License (Ms-RSL)
===========================================
The MIT License (MIT)
This license governs use of the accompanying software. If you use the software,
you accept this license. If you do not accept the license, do not use the
software.
Copyright (c) 2021 Muun Wallet, Inc.
1. Definitions
--------------
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The terms "reproduce", "reproduction" and "distribution" have the same meaning
here as under U.S. copyright law.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
"You" means the licensee of the software.
"Your company" means the company you worked for when you downloaded the
software.
"Reference use" means use of the software within your company as a reference, in
read only form, for the sole purposes of debugging your products, maintaining
your products, or enhancing the interoperability of your products with the
software, and specifically excludes the right to distribute the software outside
of your company.
Licensed patents" means any Licensor patent claims which read directly on the
software as distributed by the Licensor under this license.
2. Grant of Rights
------------------
(A) Copyright Grant - Subject to the terms of this license, the Licensor grants
you a non-transferable, non-exclusive, worldwide, royalty-free copyright license
to reproduce the software for reference use.
(B) Patent Grant - Subject to the terms of this license, the Licensor grants you
a non-transferable, non-exclusive, worldwide, royalty-free patent license under
licensed patents for reference use.
3. Limitations
--------------
(A) No Trademark License - This license does not grant you any rights to use the
Licensor's name, logo, or trademarks.
(B) If you begin patent litigation against the Licensor over patents that you
think may apply to the software (including a cross-claim or counterclaim in a
lawsuit), your license to the software ends automatically.
(C) The software is licensed "as-is." You bear the risk of using it. The
Licensor gives no express warranties, guarantees or conditions. You may have
additional consumer rights under your local laws which this license cannot
change. To the extent permitted under your local laws, the Licensor excludes the
implied warranties of merchantability, fitness for a particular purpose and
non-infringement.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -32,6 +32,15 @@ type encryptedPrivateKey struct {
Salt []byte // (optional) 8-byte salt
}
// EncryptedPrivateKeyInfo is a Gomobile-compatible version of EncryptedPrivateKey using hex-encoding.
type EncryptedPrivateKeyInfo struct {
Version int
Birthday int
EphPublicKey string
CipherText string
Salt string
}
type DecryptedPrivateKey struct {
Key *HDPrivateKey
Birthday int
@ -69,8 +78,17 @@ func (k *ChallengePrivateKey) PubKey() *ChallengePublicKey {
return &ChallengePublicKey{pubKey: k.key.PubKey()}
}
func (k *ChallengePrivateKey) DecryptKey(encryptedKey string, network *Network) (*DecryptedPrivateKey, error) {
decoded, err := decodeEncryptedPrivateKey(encryptedKey)
func (k *ChallengePrivateKey) DecryptRawKey(encryptedKey string, network *Network) (*DecryptedPrivateKey, error) {
decoded, err := DecodeEncryptedPrivateKey(encryptedKey)
if err != nil {
return nil, err
}
return k.DecryptKey(decoded, network)
}
func (k *ChallengePrivateKey) DecryptKey(decodedInfo *EncryptedPrivateKeyInfo, network *Network) (*DecryptedPrivateKey, error) {
decoded, err := unwrapEncryptedPrivateKey(decodedInfo)
if err != nil {
return nil, err
}
@ -94,7 +112,7 @@ func (k *ChallengePrivateKey) DecryptKey(encryptedKey string, network *Network)
}, nil
}
func decodeEncryptedPrivateKey(encodedKey string) (*encryptedPrivateKey, error) {
func DecodeEncryptedPrivateKey(encodedKey string) (*EncryptedPrivateKeyInfo, error) {
reader := bytes.NewReader(base58.Decode(encodedKey))
version, err := reader.ReadByte()
if err != nil {
@ -136,12 +154,12 @@ func decodeEncryptedPrivateKey(encodedKey string) (*encryptedPrivateKey, error)
}
}
result := &encryptedPrivateKey{
Version: version,
Birthday: birthday,
EphPublicKey: rawPubEph,
CipherText: ciphertext,
Salt: recoveryCodeSalt,
result := &EncryptedPrivateKeyInfo{
Version: int(version),
Birthday: int(birthday),
EphPublicKey: hex.EncodeToString(rawPubEph),
CipherText: hex.EncodeToString(ciphertext),
Salt: hex.EncodeToString(recoveryCodeSalt),
}
return result, nil
@ -150,3 +168,30 @@ func decodeEncryptedPrivateKey(encodedKey string) (*encryptedPrivateKey, error)
func shouldHaveSalt(encodedKey string) bool {
return len(encodedKey) > EncodedKeyLengthLegacy // not military-grade logic, but works for now
}
func unwrapEncryptedPrivateKey(info *EncryptedPrivateKeyInfo) (*encryptedPrivateKey, error) {
ephPublicKey, err := hex.DecodeString(info.EphPublicKey)
if err != nil {
return nil, err
}
cipherText, err := hex.DecodeString(info.CipherText)
if err != nil {
return nil, err
}
salt, err := hex.DecodeString(info.Salt)
if err != nil {
return nil, err
}
unwrapped := &encryptedPrivateKey{
Version: uint8(info.Version),
Birthday: uint16(info.Birthday),
EphPublicKey: ephPublicKey,
CipherText: cipherText,
Salt: salt,
}
return unwrapped, nil
}

View File

@ -1,7 +1,6 @@
package libwallet
import (
"encoding/hex"
"encoding/json"
"fmt"
@ -96,12 +95,12 @@ func createEmergencyKitMetadata(ekParams *EKInput) (*emergencykit.Metadata, erro
// boundary to craft the object here.
// Decode both keys, to extract their inner properties:
firstKey, err := decodeEncryptedPrivateKey(ekParams.FirstEncryptedKey)
firstKey, err := DecodeEncryptedPrivateKey(ekParams.FirstEncryptedKey)
if err != nil {
return nil, fmt.Errorf("createEkMetadata failed to decode first key: %w", err)
}
secondKey, err := decodeEncryptedPrivateKey(ekParams.SecondEncryptedKey)
secondKey, err := DecodeEncryptedPrivateKey(ekParams.SecondEncryptedKey)
if err != nil {
return nil, fmt.Errorf("createEkMetadata failed to decode second key: %w", err)
}
@ -128,10 +127,10 @@ func createEmergencyKitMetadata(ekParams *EKInput) (*emergencykit.Metadata, erro
return metadata, nil
}
func createEmergencyKitMetadataKey(key *encryptedPrivateKey) *emergencykit.MetadataKey {
func createEmergencyKitMetadataKey(key *EncryptedPrivateKeyInfo) *emergencykit.MetadataKey {
return &emergencykit.MetadataKey{
DhPubKey: hex.EncodeToString(key.EphPublicKey),
EncryptedPrivKey: hex.EncodeToString(key.CipherText),
Salt: hex.EncodeToString(key.Salt),
DhPubKey: key.EphPublicKey,
EncryptedPrivKey: key.CipherText,
Salt: key.Salt,
}
}

View File

@ -10,10 +10,9 @@ require (
github.com/lightningnetwork/lightning-onion v1.0.1
github.com/lightningnetwork/lnd v0.10.4-beta
github.com/miekg/dns v1.1.29 // indirect
github.com/pdfcpu/pdfcpu v0.3.8
github.com/pdfcpu/pdfcpu v0.3.9
github.com/pkg/errors v0.9.1
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 // indirect
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 // indirect
google.golang.org/protobuf v1.25.0
gopkg.in/gormigrate.v1 v1.6.0

View File

@ -3,7 +3,6 @@ cloud.google.com/go v0.33.1/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
git.schwanenlied.me/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e h1:F2x1bq7RaNCIuqYpswggh1+c1JmwdnkHNC9wy1KDip0=
git.schwanenlied.me/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e/go.mod h1:BWqTsj8PgcPriQJGl7el20J/7TuT1d/hSyFDXMEpoEo=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e h1:n+DcnTNkQnHlwpsrHoQtkrJIO7CBx029fw6oR4vIob4=
github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e/go.mod h1:Bdzq+51GR4/0DIhaICZEOm+OHvXGwwB2trKZ8B4Y6eQ=
github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82 h1:MG93+PZYs9PyEsj/n5/haQu2gK0h4tUtSy9ejtMwWa0=
@ -68,10 +67,6 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/champo/mobile v0.0.0-20201225234154-3393de95d3bb h1:Doj1b3qkFX5zakU7uJ1lpsER6GNS4R65Zbfrpz2fIWE=
github.com/champo/mobile v0.0.0-20201225234154-3393de95d3bb/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
github.com/champo/mobile v0.0.0-20201226003606-ef8e5756cda7 h1:jbaq2lXHNbmLj9Ab3upCbYSZ/j/TQ6yzDwie/pNyfqA=
github.com/champo/mobile v0.0.0-20201226003606-ef8e5756cda7/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -226,8 +221,8 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pdfcpu/pdfcpu v0.3.8 h1:wdKii186dzmr/aP/fkJl2s9yT3TZcwc1VqgfabNymGI=
github.com/pdfcpu/pdfcpu v0.3.8/go.mod h1:EfJ1EIo3n5+YlGF53DGe1yF1wQLiqK1eqGDN5LuKALs=
github.com/pdfcpu/pdfcpu v0.3.9 h1:gHPreswsOGwe1zViJxufbvNZf0xhK4mxj/r1CwLp958=
github.com/pdfcpu/pdfcpu v0.3.9/go.mod h1:EfJ1EIo3n5+YlGF53DGe1yF1wQLiqK1eqGDN5LuKALs=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -269,16 +264,12 @@ golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20190823064033-3a9bac650e44/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@ -286,10 +277,7 @@ golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTk
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd h1:ePuNC7PZ6O5BzgPn9bZayERXBdfZjUYoXEf5BTfDfh8=
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -312,6 +300,7 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -328,19 +317,15 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY=
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69 h1:yBHHx+XZqXJBm6Exke3N7V9gnlsyXxoCPEb1yVenjfk=
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -368,8 +353,10 @@ google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v1 v1.0.1/go.mod h1:3NjfXwocQRYAPTq4/fzX+CwUhPRcR/azYRhj8G+LqMo=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gormigrate.v1 v1.6.0 h1:XpYM6RHQPmzwY7Uyu+t+xxMXc86JYFJn4nEc9HzQjsI=
gopkg.in/gormigrate.v1 v1.6.0/go.mod h1:Lf00lQrHqfSYWiTtPcyQabsDdM6ejZaMgV0OU6JMSlw=
@ -377,6 +364,7 @@ gopkg.in/macaroon-bakery.v2 v2.0.1/go.mod h1:B4/T17l+ZWGwxFSZQmlBwp25x+og7OkhETf
gopkg.in/macaroon.v2 v2.0.0/go.mod h1:+I6LnTMkm/uV5ew/0nsulNjL16SK4+C8yDmRUzHR17I=
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -41,22 +41,23 @@ func Validate(
if err != nil {
return err
}
amountToForward := payload.ForwardingInfo().AmountToForward
if amount != 0 && amountToForward > amount {
return fmt.Errorf(
"sphinx payment amount does not match (%v != %v)", amount, amountToForward,
)
}
// Validate payment secret if it exists
if payload.MPP != nil {
paymentAddr := payload.MPP.PaymentAddr()
amountToForward := payload.ForwardingInfo().AmountToForward
total := payload.MultiPath().TotalMsat()
if !bytes.Equal(paymentAddr[:], paymentSecret) {
return errors.New("sphinx payment secret does not match")
}
if amount != 0 && amountToForward > amount {
return fmt.Errorf(
"sphinx payment amount does not match (%v != %v)", amount, amountToForward,
)
}
if amountToForward < total {
return fmt.Errorf("payment is multipart. forwarded amt = %v, total amt = %v", amountToForward, total)
}

149
vendor/github.com/pdfcpu/pdfcpu/pkg/api/booklet.go generated vendored Normal file
View File

@ -0,0 +1,149 @@
/*
Copyright 2021 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"
"time"
"github.com/pdfcpu/pdfcpu/pkg/log"
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu"
)
// BookletFromImages creates a booklet from images.
func BookletFromImages(conf *pdfcpu.Configuration, imageFileNames []string, nup *pdfcpu.NUp) (*pdfcpu.Context, error) {
if nup.PageDim == nil {
// Set default paper size.
nup.PageDim = pdfcpu.PaperSize[nup.PageSize]
}
ctx, err := pdfcpu.CreateContextWithXRefTable(conf, nup.PageDim)
if err != nil {
return nil, err
}
pagesIndRef, err := ctx.Pages()
if err != nil {
return nil, err
}
// This is the page tree root.
pagesDict, err := ctx.DereferenceDict(*pagesIndRef)
if err != nil {
return nil, err
}
err = pdfcpu.BookletFromImages(ctx, imageFileNames, nup, pagesDict, pagesIndRef)
return ctx, err
}
// Booklet arranges PDF pages on larger sheets of paper and writes the result to w.
func Booklet(rs io.ReadSeeker, w io.Writer, imgFiles, selectedPages []string, nup *pdfcpu.NUp, conf *pdfcpu.Configuration) error {
if conf == nil {
conf = pdfcpu.NewDefaultConfiguration()
}
conf.Cmd = pdfcpu.BOOKLET
log.Info.Printf("%s", nup)
// below is very similar to api.NUp
var (
ctx *pdfcpu.Context
err error
)
if nup.ImgInputFile {
if ctx, err = BookletFromImages(conf, imgFiles, nup); err != nil {
return err
}
} else {
if ctx, _, _, err = readAndValidate(rs, conf, time.Now()); err != nil {
return err
}
if err := ctx.EnsurePageCount(); err != nil {
return err
}
pages, err := PagesForPageSelection(ctx.PageCount, selectedPages, true)
if err != nil {
return err
}
if err = ctx.BookletFromPDF(pages, nup); err != nil {
return err
}
}
if conf.ValidationMode != pdfcpu.ValidationNone {
if err = ValidateContext(ctx); err != nil {
return err
}
}
if err = WriteContext(ctx, w); err != nil {
return err
}
log.Stats.Printf("XRefTable:\n%s\n", ctx)
return nil
}
// BookletFile rearranges PDF pages or images into a booklet layout and writes the result to outFile.
func BookletFile(inFiles []string, outFile string, selectedPages []string, nup *pdfcpu.NUp, conf *pdfcpu.Configuration) (err error) {
//if nup.ImgInputFile {
// return fmt.Errorf("image file input not yet supported for booklet")
//}
var f1, f2 *os.File
// booklet from a PDF
if f1, err = os.Open(inFiles[0]); err != nil {
return err
}
if f2, err = os.Create(outFile); err != nil {
return err
}
log.CLI.Printf("writing %s...\n", outFile)
defer func() {
if err != nil {
if f1 != nil {
f1.Close()
}
f2.Close()
return
}
if f1 != nil {
if err = f1.Close(); err != nil {
return
}
}
err = f2.Close()
return
}()
return Booklet(f1, f2, inFiles, selectedPages, nup, conf)
}

View File

@ -25,26 +25,36 @@ import (
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu"
)
// PDFNUp returns an NUp configuration for Nup-ing PDF files.
func PDFNUp(val int, desc string) (*pdfcpu.NUp, error) {
// PDFNUpConfig returns an NUp configuration for Nup-ing PDF files.
func PDFNUpConfig(val int, desc string) (*pdfcpu.NUp, error) {
return pdfcpu.PDFNUpConfig(val, desc)
}
// ImageNUp returns an NUp configuration for Nup-ing image files.
func ImageNUp(val int, desc string) (*pdfcpu.NUp, error) {
// ImageNUpConfig returns an NUp configuration for Nup-ing image files.
func ImageNUpConfig(val int, desc string) (*pdfcpu.NUp, error) {
return pdfcpu.ImageNUpConfig(val, desc)
}
// PDFGrid returns a grid configuration for Nup-ing PDF files.
func PDFGrid(rows, cols int, desc string) (*pdfcpu.NUp, error) {
// PDFGridConfig returns a grid configuration for Grid-ing PDF files.
func PDFGridConfig(rows, cols int, desc string) (*pdfcpu.NUp, error) {
return pdfcpu.PDFGridConfig(rows, cols, desc)
}
// ImageGrid returns a grid configuration for Nup-ing image files.
func ImageGrid(rows, cols int, desc string) (*pdfcpu.NUp, error) {
// ImageGridConfig returns a grid configuration for Grid-ing image files.
func ImageGridConfig(rows, cols int, desc string) (*pdfcpu.NUp, error) {
return pdfcpu.ImageGridConfig(rows, cols, desc)
}
// PDFBookletConfig returns an NUp configuration for Booklet-ing PDF files.
func PDFBookletConfig(val int, desc string) (*pdfcpu.NUp, error) {
return pdfcpu.PDFBookletConfig(val, desc)
}
// ImageBookletConfig returns an NUp configuration for Booklet-ing image files.
func ImageBookletConfig(val int, desc string) (*pdfcpu.NUp, error) {
return pdfcpu.ImageBookletConfig(val, desc)
}
// NUpFromImage creates a single page n-up PDF for one image
// or a sequence of n-up pages for more than one image.
func NUpFromImage(conf *pdfcpu.Configuration, imageFileNames []string, nup *pdfcpu.NUp) (*pdfcpu.Context, error) {

View File

@ -296,7 +296,7 @@ func (t table) parseNamingTable(fd *ttf) error {
// table "name"
count := int(t.uint16(2))
stringOffset := t.uint16(4)
nameID := uint16(0)
var nameID uint16
baseOff := 6
for i := 0; i < count; i++ {
recOff := baseOff + i*12
@ -406,7 +406,7 @@ func (t table) parseCMapFormat4(fd *ttf) error {
}
idDelta := uint32(t.uint16(deltaOff + i*2))
idRangeOff := int(t.uint16(rangeOff + i*2))
v := uint16(0)
var v uint16
for c, j := startCode, 0; c <= endCode && c != 0xFFFF; c++ {
if idRangeOff > 0 {
v = t.uint16(rangeOff + i*2 + idRangeOff + j*2)

416
vendor/github.com/pdfcpu/pdfcpu/pkg/pdfcpu/booklet.go generated vendored Normal file
View File

@ -0,0 +1,416 @@
/*
Copyright 2021 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 pdfcpu
import (
"bytes"
"fmt"
"io"
"os"
"github.com/pkg/errors"
)
// DefaultBookletConfig returns the default configuration for a booklet
func DefaultBookletConfig() *NUp {
nup := DefaultNUpConfig()
nup.Margin = 0
nup.Border = false
nup.BookletGuides = false
return nup
}
// PDFBookletConfig returns an NUp configuration for booklet-ing PDF files.
func PDFBookletConfig(val int, desc string) (*NUp, error) {
nup := DefaultBookletConfig()
if desc != "" {
if err := ParseNUpDetails(desc, nup); err != nil {
return nil, err
}
}
return nup, ParseNUpValue(val, nup)
}
// ImageBookletConfig returns an NUp configuration for booklet-ing image files.
func ImageBookletConfig(val int, desc string) (*NUp, error) {
nup, err := PDFBookletConfig(val, desc)
if err != nil {
return nil, err
}
nup.ImgInputFile = true
return nup, nil
}
func getPageNumber(pageNumbers []int, n int) int {
if n >= len(pageNumbers) {
// Zero represents blank page at end of booklet.
return 0
}
return pageNumbers[n]
}
func drawGuideLineLabel(w io.Writer, x, y float64, s string, mb *Rectangle, fm FontMap, rot int) {
fontName := "Helvetica"
td := TextDescriptor{
FontName: fontName,
FontKey: fm.EnsureKey(fontName),
FontSize: 9,
Scale: 1.0,
ScaleAbs: true,
StrokeCol: Black,
FillCol: Black,
X: x,
Y: y,
Rotation: float64(rot),
Text: s,
}
WriteMultiLine(w, mb, nil, td)
}
func drawScissor(w io.Writer, mb *Rectangle, fm FontMap) {
fontName := "ZapfDingbats"
td := TextDescriptor{
FontName: fontName,
FontKey: fm.EnsureKey(fontName),
FontSize: 12,
Scale: 1.0,
ScaleAbs: true,
StrokeCol: Black,
FillCol: Black,
X: 0,
Y: mb.Height()/2 - 4,
Text: string([]byte{byte(34)}),
}
WriteMultiLine(w, mb, nil, td)
}
func drawBookletGuides(nup *NUp, w io.Writer) FontMap {
width := nup.PageDim.Width
height := nup.PageDim.Height
var fm FontMap = FontMap{}
mb := RectForDim(nup.PageDim.Width, nup.PageDim.Height)
SetLineWidth(w, 0)
SetStrokeColor(w, Gray)
switch nup.N() {
case 2:
// Draw horizontal folding line.
fmt.Fprint(w, "[3] 0 d ")
DrawLine(w, 0, height/2, width, height/2)
drawGuideLineLabel(w, 1, height/2+2, "Fold here", mb, fm, 0)
case 4:
// Draw vertical folding line.
fmt.Fprint(w, "[3] 0 d ")
DrawLine(w, width/2, 0, width/2, height)
drawGuideLineLabel(w, width/2-23, 20, "Fold here", mb, fm, 90)
// Draw horizontal cutting line.
fmt.Fprint(w, "[3] 0 d ")
DrawLine(w, 0, height/2, width, height/2)
drawGuideLineLabel(w, width, height/2+2, "Fold & Cut here", mb, fm, 0)
// Draw scissors over cutting line.
drawScissor(w, mb, fm)
}
return fm
}
type bookletPage struct {
number int
rotate bool
}
func nup2OutputPageNr(inputPageNr, inputPageCount int, pageNumbers []int) (int, bool) {
var p int
if inputPageNr%2 == 0 {
p = inputPageCount - 1 - inputPageNr/2
} else {
p = (inputPageNr - 1) / 2
}
pageNr := getPageNumber(pageNumbers, p)
// Rotate odd output pages (the back sides) by 180 degrees.
var rotate bool
if inputPageNr%4 < 2 {
rotate = true
}
return pageNr, rotate
}
func nup4OutputPageNr(inputPageNr int, inputPageCount int, pageNumbers []int) (int, bool) {
bookletPageNumber := inputPageNr / 4
var p int
if bookletPageNumber%2 == 0 {
// front side
switch inputPageNr % 4 {
case 0:
p = inputPageCount - 1 - bookletPageNumber
case 1:
p = bookletPageNumber
case 2:
p = inputPageCount/2 + bookletPageNumber
case 3:
p = inputPageCount/2 - 1 - bookletPageNumber
}
} else {
// back side
switch inputPageNr % 4 {
case 0:
p = bookletPageNumber
case 1:
p = inputPageCount - 1 - bookletPageNumber
case 2:
p = inputPageCount/2 - 1 - bookletPageNumber
case 3:
p = inputPageCount/2 + bookletPageNumber
}
}
pageNr := getPageNumber(pageNumbers, p)
// Rotate bottom row of each output page by 180 degrees.
var rotate bool
if inputPageNr%4 >= 2 {
rotate = true
}
return pageNr, rotate
}
func sortSelectedPagesForBooklet(pages IntSet, nup *NUp) []bookletPage {
pageNumbers := sortSelectedPages(pages)
pageCount := len(pageNumbers)
// A sheet of paper consists of 2 consecutive output pages.
sheetPageCount := 2 * nup.N()
// pageCount must be a multiple of the number of pages per sheet.
// If not, we will insert blank pages at the end of the booklet.
if pageCount%sheetPageCount != 0 {
pageCount += sheetPageCount - pageCount%sheetPageCount
}
bookletPages := make([]bookletPage, pageCount)
switch nup.N() {
case 2:
// (output page, input page) = [(1,n), (2,1), (3, n-1), (4, 2), (5, n-2), (6, 3), ...]
for i := 0; i < pageCount; i++ {
pageNr, rotate := nup2OutputPageNr(i, pageCount, pageNumbers)
bookletPages[i].number = pageNr
bookletPages[i].rotate = rotate
}
case 4:
// (output page, input page) = [(1,n), (2,1), (3, n/2+1), (4, n/2-0), (5, 2), (6, n-1), (7, n/2-1), (8, n/2+2) ...]
for i := 0; i < pageCount; i++ {
pageNr, rotate := nup4OutputPageNr(i, pageCount, pageNumbers)
bookletPages[i].number = pageNr
bookletPages[i].rotate = rotate
}
}
return bookletPages
}
func (ctx *Context) bookletPages(selectedPages IntSet, nup *NUp, pagesDict Dict, pagesIndRef *IndirectRef) error {
xRefTable := ctx.XRefTable
var buf bytes.Buffer
formsResDict := NewDict()
rr := rectsForGrid(nup)
for i, bp := range sortSelectedPagesForBooklet(selectedPages, nup) {
if i > 0 && i%len(rr) == 0 {
// Wrap complete booklet page.
if err := wrapUpPage(ctx, nup, formsResDict, buf, pagesDict, pagesIndRef); err != nil {
return err
}
buf.Reset()
formsResDict = NewDict()
}
rDest := rr[i%len(rr)]
if bp.number == 0 {
// This is an empty page at the end of a booklet.
if nup.BgColor != nil {
FillRectStacked(&buf, rDest, *nup.BgColor)
}
continue
}
consolidateRes := true
d, inhPAttrs, err := ctx.PageDict(bp.number, consolidateRes)
if err != nil {
return err
}
if d == nil {
return errors.Errorf("pdfcpu: unknown page number: %d\n", i)
}
// Retrieve content stream bytes.
bb, err := xRefTable.PageContent(d)
if err == errNoContent {
continue
}
if err != nil {
return err
}
// Create an object for this resDict in xRefTable.
ir, err := ctx.IndRefForNewObject(inhPAttrs.resources)
if err != nil {
return err
}
cropBox := inhPAttrs.mediaBox
if inhPAttrs.cropBox != nil {
cropBox = inhPAttrs.cropBox
}
formIndRef, err := createNUpFormForPDF(xRefTable, ir, bb, cropBox)
if err != nil {
return err
}
formResID := fmt.Sprintf("Fm%d", i)
formsResDict.Insert(formResID, *formIndRef)
// Append to content stream of page i.
nUpTilePDFBytes(&buf, cropBox, rDest, formResID, nup, bp.rotate)
}
// Wrap incomplete booklet page.
return wrapUpPage(ctx, nup, formsResDict, buf, pagesDict, pagesIndRef)
}
// BookletFromPDF creates a booklet version of the PDF represented by xRefTable.
func (ctx *Context) BookletFromPDF(selectedPages IntSet, nup *NUp) error {
n := int(nup.Grid.Width * nup.Grid.Height)
if !(n == 2 || n == 4) {
return fmt.Errorf("pdfcpu: booklet must have n={2,4} pages per sheet, got %d", n)
}
var mb *Rectangle
if nup.PageDim == nil {
nup.PageDim = PaperSize[nup.PageSize]
}
mb = RectForDim(nup.PageDim.Width, nup.PageDim.Height)
pagesDict := Dict(
map[string]Object{
"Type": Name("Pages"),
"Count": Integer(0),
"MediaBox": mb.Array(),
},
)
pagesIndRef, err := ctx.IndRefForNewObject(pagesDict)
if err != nil {
return err
}
nup.PageDim = &Dim{mb.Width(), mb.Height()}
if err = ctx.bookletPages(selectedPages, nup, pagesDict, pagesIndRef); err != nil {
return err
}
// Replace original pagesDict.
rootDict, err := ctx.Catalog()
if err != nil {
return err
}
rootDict.Update("Pages", *pagesIndRef)
return nil
}
// BookletFromImages creates a booklet version of the image sequence represented by fileNames.
func BookletFromImages(ctx *Context, fileNames []string, nup *NUp, pagesDict Dict, pagesIndRef *IndirectRef) error {
// The order of images in fileNames corresponds to a desired booklet page sequence.
selectedPages := IntSet{}
for i := 1; i <= len(fileNames); i++ {
selectedPages[i] = true
}
if nup.PageGrid {
nup.PageDim.Width *= nup.Grid.Width
nup.PageDim.Height *= nup.Grid.Height
}
xRefTable := ctx.XRefTable
formsResDict := NewDict()
var buf bytes.Buffer
rr := rectsForGrid(nup)
for i, bp := range sortSelectedPagesForBooklet(selectedPages, nup) {
if i > 0 && i%len(rr) == 0 {
// Wrap complete booklet page.
if err := wrapUpPage(ctx, nup, formsResDict, buf, pagesDict, pagesIndRef); err != nil {
return err
}
buf.Reset()
formsResDict = NewDict()
}
rDest := rr[i%len(rr)]
if bp.number == 0 {
// This is an empty page at the end of a booklet.
if nup.BgColor != nil {
FillRectStacked(&buf, rDest, *nup.BgColor)
}
continue
}
f, err := os.Open(fileNames[bp.number-1])
if err != nil {
return err
}
imgIndRef, w, h, err := createImageResource(xRefTable, f)
if err != nil {
return err
}
if err := f.Close(); err != nil {
return err
}
formIndRef, err := createNUpFormForImage(xRefTable, imgIndRef, w, h, i)
if err != nil {
return err
}
formResID := fmt.Sprintf("Fm%d", i)
formsResDict.Insert(formResID, *formIndRef)
// Append to content stream of booklet page i.
nUpTilePDFBytes(&buf, RectForDim(float64(w), float64(h)), rr[i%len(rr)], formResID, nup, bp.rotate)
}
// Wrap incomplete booklet page.
return wrapUpPage(ctx, nup, formsResDict, buf, pagesDict, pagesIndRef)
}

View File

@ -16,7 +16,11 @@ limitations under the License.
package pdfcpu
import "github.com/pkg/errors"
import (
"strings"
"github.com/pkg/errors"
)
var (
errNoBookmarks = errors.New("pdfcpu: no bookmarks available")
@ -75,6 +79,33 @@ func (ctx *Context) positionToOutlineTreeLevel1() (Dict, *IndirectRef, error) {
return d, first, nil
}
func (ctx *Context) dereferenceDestPageNumber(dest Object) (IndirectRef, error) {
var ir IndirectRef
switch dest := dest.(type) {
case Name:
arr, err := ctx.dereferenceDestinationArray(dest.Value())
if err != nil {
return ir, err
}
ir = arr[0].(IndirectRef)
case StringLiteral:
arr, err := ctx.dereferenceDestinationArray(dest.Value())
if err != nil {
return ir, err
}
ir = arr[0].(IndirectRef)
case HexLiteral:
arr, err := ctx.dereferenceDestinationArray(dest.Value())
if err != nil {
return ir, err
}
ir = arr[0].(IndirectRef)
case Array:
ir = dest[0].(IndirectRef)
}
return ir, nil
}
// BookmarksForOutlineLevel1 returns bookmarks incliuding page span info.
func (ctx *Context) BookmarksForOutlineLevel1() ([]Bookmark, error) {
d, first, err := ctx.positionToOutlineTreeLevel1()
@ -91,39 +122,29 @@ func (ctx *Context) BookmarksForOutlineLevel1() ([]Bookmark, error) {
return nil, err
}
title, _ := Text(d["Title"])
s, _ := Text(d["Title"])
var sb strings.Builder
for i := 0; i < len(s); i++ {
b := s[i]
if b >= 32 {
if b == 32 {
b = '_'
}
sb.WriteByte(b)
}
}
title := sb.String()
dest, found := d["Dest"]
if !found {
return nil, errNoBookmarks
}
var ir IndirectRef
dest, _ = ctx.Dereference(dest)
switch dest := dest.(type) {
case Name:
arr, err := ctx.dereferenceDestinationArray(dest.Value())
if err != nil {
return nil, err
}
ir = arr[0].(IndirectRef)
case StringLiteral:
arr, err := ctx.dereferenceDestinationArray(dest.Value())
if err != nil {
return nil, err
}
ir = arr[0].(IndirectRef)
case HexLiteral:
arr, err := ctx.dereferenceDestinationArray(dest.Value())
if err != nil {
return nil, err
}
ir = arr[0].(IndirectRef)
case Array:
ir = dest[0].(IndirectRef)
ir, err := ctx.dereferenceDestPageNumber(dest)
if err != nil {
return nil, err
}
pageFrom, err := ctx.PageNumber(ir.ObjectNumber.Value())

View File

@ -487,7 +487,7 @@ func parseBoxBy3MarginVals(s, s1, s2, s3 string, abs bool, u DisplayUnit) (*Box,
}
func parseBoxBy4Percentages(s, s1, s2, s3, s4 string) (*Box, error) {
// 10% 15.5% 10%
// 10% 15% 15% 10%
// Parse top margin.
s1 = s1[:len(s1)-1]
if len(s1) == 0 {
@ -499,23 +499,24 @@ func parseBoxBy4Percentages(s, s1, s2, s3, s4 string) (*Box, error) {
}
tm := pct / 100
// Parse right margin.
if s2[len(s2)-1] != '%' {
return nil, errors.Errorf("pdfcpu: invalid box definition: %s", s)
}
// Parse hor margin.
s2 = s2[:len(s2)-1]
if len(s2) == 0 {
return nil, errors.Errorf("pdfcpu: invalid box definition: %s", s)
}
hm, err := parseBoxPercentage(s2)
pct, err = strconv.ParseFloat(s1, 64)
if err != nil {
return nil, err
}
rm := pct / 100
// Parse bottom margin.
if s3[len(s3)-1] != '%' {
return nil, errors.Errorf("pdfcpu: invalid box definition: %s", s)
}
// Parse bottom margin.
s3 = s3[:len(s3)-1]
if len(s3) == 0 {
return nil, errors.Errorf("pdfcpu: invalid box definition: %s", s)
@ -525,11 +526,29 @@ func parseBoxBy4Percentages(s, s1, s2, s3, s4 string) (*Box, error) {
return nil, err
}
bm := pct / 100
// Parse left margin.
if s4[len(s4)-1] != '%' {
return nil, errors.Errorf("pdfcpu: invalid box definition: %s", s)
}
s4 = s4[:len(s4)-1]
if len(s4) == 0 {
return nil, errors.Errorf("pdfcpu: invalid box definition: %s", s)
}
pct, err = strconv.ParseFloat(s3, 64)
if err != nil {
return nil, err
}
lm := pct / 100
if tm+bm >= 1 {
return nil, errors.Errorf("pdfcpu: vertical margin overflow: %s", s)
}
if rm+lm >= 1 {
return nil, errors.Errorf("pdfcpu: horizontal margin overflow: %s", s)
}
return &Box{MLeft: hm, MRight: hm, MTop: tm, MBot: bm}, nil
return &Box{MLeft: lm, MRight: rm, MTop: tm, MBot: bm}, nil
}
func parseBoxBy4MarginVals(s, s1, s2, s3, s4 string, abs bool, u DisplayUnit) (*Box, error) {
@ -554,14 +573,14 @@ func parseBoxBy4MarginVals(s, s1, s2, s3, s4 string, abs bool, u DisplayUnit) (*
return nil, err
}
// Parse botton margin.
// Parse bottom margin.
bm, err := strconv.ParseFloat(s3, 64)
if err != nil {
return nil, err
}
// Parse left margin.
lm, err := strconv.ParseFloat(s1, 64)
lm, err := strconv.ParseFloat(s4, 64)
if err != nil {
return nil, err
}

View File

@ -85,6 +85,7 @@ const (
REMOVEPAGES
ROTATE
NUP
BOOKLET
INFO
CHEATSHEETSFONTS
INSTALLFONTS

View File

@ -19,6 +19,7 @@ package pdfcpu
import (
"bytes"
"fmt"
"io"
"math"
"strconv"
"strings"
@ -186,64 +187,73 @@ func decodeUTF8ToByte(s string) string {
}
// SetLineJoinStyle sets the line join style for stroking operations.
func SetLineJoinStyle(b *bytes.Buffer, s LineJoinStyle) {
b.WriteString(fmt.Sprintf("%d j ", s))
func SetLineJoinStyle(w io.Writer, s LineJoinStyle) {
fmt.Fprintf(w, "%d j ", s)
}
// SetLineWidth sets line width for stroking operations.
func SetLineWidth(b *bytes.Buffer, w float64) {
b.WriteString(fmt.Sprintf("%.2f w ", w))
func SetLineWidth(w io.Writer, width float64) {
fmt.Fprintf(w, "%.2f w ", width)
}
// DrawLine draws the path from P to Q.
func DrawLine(b *bytes.Buffer, xp, yp, xq, yq float64) {
b.WriteString(fmt.Sprintf("%.2f %.2f m %.2f %.2f l s ", xp, yp, xq, yq))
func DrawLine(w io.Writer, xp, yp, xq, yq float64) {
fmt.Fprintf(w, "%.2f %.2f m %.2f %.2f l s ", xp, yp, xq, yq)
}
// DrawRect strokes a rectangular path for r.
func DrawRect(b *bytes.Buffer, r *Rectangle) {
b.WriteString(fmt.Sprintf("%.2f %.2f %.2f %.2f re s ", r.LL.X, r.LL.Y, r.Width(), r.Height()))
func DrawRect(w io.Writer, r *Rectangle) {
fmt.Fprintf(w, "%.2f %.2f %.2f %.2f re s ", r.LL.X, r.LL.Y, r.Width(), r.Height())
}
// DrawAndFillRect strokes and fills a rectangular path for r.
func DrawAndFillRect(b *bytes.Buffer, r *Rectangle) {
b.WriteString(fmt.Sprintf("%.2f %.2f %.2f %.2f re B ", r.LL.X, r.LL.Y, r.Width(), r.Height()))
func DrawAndFillRect(w io.Writer, r *Rectangle) {
fmt.Fprintf(w, "%.2f %.2f %.2f %.2f re B ", r.LL.X, r.LL.Y, r.Width(), r.Height())
}
// SetFillColor sets the fill color.
func SetFillColor(bb *bytes.Buffer, c SimpleColor) {
bb.WriteString(fmt.Sprintf("%.2f %.2f %.2f rg ", c.R, c.G, c.B))
func SetFillColor(w io.Writer, c SimpleColor) {
fmt.Fprintf(w, "%.2f %.2f %.2f rg ", c.R, c.G, c.B)
}
// SetStrokeColor sets the stroke color.
func SetStrokeColor(bb *bytes.Buffer, c SimpleColor) {
bb.WriteString(fmt.Sprintf("%.2f %.2f %.2f RG ", c.R, c.G, c.B))
func SetStrokeColor(w io.Writer, c SimpleColor) {
fmt.Fprintf(w, "%.2f %.2f %.2f RG ", c.R, c.G, c.B)
}
// FillRect draws and fills a rectangle using r, g, b.
func FillRect(bb *bytes.Buffer, rect *Rectangle, c SimpleColor) {
SetFillColor(bb, c)
DrawAndFillRect(bb, rect)
func FillRect(w io.Writer, rect *Rectangle, c SimpleColor) {
SetFillColor(w, c)
DrawAndFillRect(w, rect)
}
// FillRectStacked is a safe way to fill a rectangle within a page content stream.
func FillRectStacked(w io.Writer, r *Rectangle, c SimpleColor) {
fmt.Fprintf(w, "q ")
SetLineWidth(w, 0)
SetStrokeColor(w, c)
FillRect(w, r, c)
fmt.Fprintf(w, "Q ")
}
// DrawGrid draws an x * y grid on r using strokeCol and fillCol.
func DrawGrid(bb *bytes.Buffer, x, y int, r *Rectangle, strokeCol SimpleColor, fillCol *SimpleColor) {
SetLineWidth(bb, 0)
SetStrokeColor(bb, strokeCol)
func DrawGrid(w io.Writer, x, y int, r *Rectangle, strokeCol SimpleColor, fillCol *SimpleColor) {
SetLineWidth(w, 0)
SetStrokeColor(w, strokeCol)
if fillCol != nil {
FillRect(bb, r, *fillCol)
FillRect(w, r, *fillCol)
}
s := r.Width() / float64(x)
for i := 0; i <= x; i++ {
x := r.LL.X + float64(i)*s
DrawLine(bb, x, r.LL.Y, x, r.UR.Y)
DrawLine(w, x, r.LL.Y, x, r.UR.Y)
}
s = r.Height() / float64(y)
for i := 0; i <= y; i++ {
y := r.LL.Y + float64(i)*s
DrawLine(bb, r.LL.X, y, r.UR.X, y)
DrawLine(w, r.LL.X, y, r.UR.X, y)
}
}
@ -314,7 +324,7 @@ func calcBoundingBoxForJLines(lines []string, x, y, w float64, fontName string,
}
// DrawHairCross draw a haircross with origin x/y.
func DrawHairCross(buf *bytes.Buffer, x, y float64, r *Rectangle) {
func DrawHairCross(w io.Writer, x, y float64, r *Rectangle) {
x1, y1 := x, y
if x == 0 {
x1 = r.LL.X + r.Width()/2
@ -322,10 +332,10 @@ func DrawHairCross(buf *bytes.Buffer, x, y float64, r *Rectangle) {
if y == 0 {
y1 = r.LL.Y + r.Height()/2
}
SetLineWidth(buf, 0)
SetStrokeColor(buf, Black)
DrawLine(buf, r.LL.X, y1, r.LL.X+r.Width(), y1) // Horizontal line
DrawLine(buf, x1, r.LL.Y, x1, r.LL.Y+r.Height()) // Vertical line
SetLineWidth(w, 0)
SetStrokeColor(w, Black)
DrawLine(w, r.LL.X, y1, r.LL.X+r.Width(), y1) // Horizontal line
DrawLine(w, x1, r.LL.Y, x1, r.LL.Y+r.Height()) // Vertical line
}
func prepBytes(s, fontName string) string {
@ -346,14 +356,14 @@ func prepBytes(s, fontName string) string {
return *s1
}
func writeStringToBuf(buf *bytes.Buffer, s string, x, y float64, strokeCol, fillCol SimpleColor, rm RenderMode, fontName string) {
func writeStringToBuf(w io.Writer, s string, x, y float64, strokeCol, fillCol SimpleColor, rm RenderMode, fontName string) {
s = prepBytes(s, fontName)
buf.WriteString(fmt.Sprintf("BT 0 Tw %.2f %.2f %.2f RG %.2f %.2f %.2f rg %.2f %.2f Td %d Tr (%s) Tj ET ",
strokeCol.R, strokeCol.G, strokeCol.B, fillCol.R, fillCol.G, fillCol.B, x, y, rm, s))
fmt.Fprintf(w, "BT 0 Tw %.2f %.2f %.2f RG %.2f %.2f %.2f rg %.2f %.2f Td %d Tr (%s) Tj ET ",
strokeCol.R, strokeCol.G, strokeCol.B, fillCol.R, fillCol.G, fillCol.B, x, y, rm, s)
}
func setFont(b *bytes.Buffer, fontID string, fontSize float32) {
b.WriteString(fmt.Sprintf("BT /%s %.2f Tf ET ", fontID, fontSize))
func setFont(w io.Writer, fontID string, fontSize float32) {
fmt.Fprintf(w, "BT /%s %.2f Tf ET ", fontID, fontSize)
}
func calcBoundingBox(s string, x, y float64, fontName string, fontSize int) *Rectangle {
@ -632,9 +642,9 @@ func createBoundingBoxForColumn(r *Rectangle, x, y *float64,
return box
}
func flushJustifiedStringToBuf(buf *bytes.Buffer, s string, x, y float64, strokeCol, fillCol SimpleColor, rm RenderMode) {
buf.WriteString(fmt.Sprintf("BT 0 Tw %.2f %.2f %.2f RG %.2f %.2f %.2f rg %.2f %.2f Td %d Tr %s ET ",
strokeCol.R, strokeCol.G, strokeCol.B, fillCol.R, fillCol.G, fillCol.B, x, y, rm, s))
func flushJustifiedStringToBuf(w io.Writer, s string, x, y float64, strokeCol, fillCol SimpleColor, rm RenderMode) {
fmt.Fprintf(w, "BT 0 Tw %.2f %.2f %.2f RG %.2f %.2f %.2f rg %.2f %.2f Td %d Tr %s ET ",
strokeCol.R, strokeCol.G, strokeCol.B, fillCol.R, fillCol.G, fillCol.B, x, y, rm, s)
}
func scaleXForRegion(x float64, mediaBox, region *Rectangle) float64 {
@ -645,43 +655,59 @@ func scaleYForRegion(y float64, mediaBox, region *Rectangle) float64 {
return y / mediaBox.Width() * region.Width()
}
func drawMargins(buf *bytes.Buffer, c SimpleColor, colBB *Rectangle, borderWidth, mLeft, mRight, mTop, mBot float64) {
SetLineWidth(buf, 0)
SetStrokeColor(buf, c)
func drawMargins(w io.Writer, c SimpleColor, colBB *Rectangle, borderWidth, mLeft, mRight, mTop, mBot float64) {
if mLeft <= 0 && mRight <= 0 && mTop <= 0 && mBot <= 0 {
return
}
r := RectForWidthAndHeight(colBB.LL.X+borderWidth, colBB.LL.Y+borderWidth, colBB.Width()-2*borderWidth, mBot)
FillRect(buf, r, c)
fmt.Fprintf(w, "q ")
SetLineWidth(w, 0)
SetStrokeColor(w, c)
var r *Rectangle
r = RectForWidthAndHeight(colBB.LL.X+borderWidth, colBB.Height()-borderWidth-mTop, colBB.Width()-2*borderWidth, mTop)
FillRect(buf, r, c)
if mBot > 0 {
r = RectForWidthAndHeight(colBB.LL.X+borderWidth, colBB.LL.Y+borderWidth, colBB.Width()-2*borderWidth, mBot)
FillRect(w, r, c)
}
r = RectForWidthAndHeight(colBB.LL.X+borderWidth, colBB.LL.Y+borderWidth+mBot, mLeft, colBB.Height()-2*borderWidth-mTop-mBot)
FillRect(buf, r, c)
if mTop > 0 {
r = RectForWidthAndHeight(colBB.LL.X+borderWidth, colBB.UR.Y-borderWidth-mTop, colBB.Width()-2*borderWidth, mTop)
FillRect(w, r, c)
}
r = RectForWidthAndHeight(colBB.UR.X-borderWidth-mRight, colBB.LL.Y+borderWidth+mBot, mRight, colBB.Height()-2*borderWidth-mTop-mBot)
FillRect(buf, r, c)
if mLeft > 0 {
r = RectForWidthAndHeight(colBB.LL.X+borderWidth, colBB.LL.Y+borderWidth+mBot, mLeft, colBB.Height()-2*borderWidth-mTop-mBot)
FillRect(w, r, c)
}
if mRight > 0 {
r = RectForWidthAndHeight(colBB.UR.X-borderWidth-mRight, colBB.LL.Y+borderWidth+mBot, mRight, colBB.Height()-2*borderWidth-mTop-mBot)
FillRect(w, r, c)
}
fmt.Fprintf(w, "Q ")
}
func renderBackgroundAndBorder(buf *bytes.Buffer, td TextDescriptor, borderWidth float64, colBB *Rectangle) {
SetLineJoinStyle(buf, td.BorderStyle)
func renderBackgroundAndBorder(w io.Writer, td TextDescriptor, borderWidth float64, colBB *Rectangle) {
SetLineJoinStyle(w, td.BorderStyle)
if td.ShowBackground {
SetLineWidth(buf, borderWidth)
SetLineWidth(w, borderWidth)
c := td.BackgroundCol
if td.ShowBorder {
c = td.BorderCol
}
SetStrokeColor(buf, c)
SetStrokeColor(w, c)
r := RectForWidthAndHeight(colBB.LL.X+borderWidth/2, colBB.LL.Y+borderWidth/2, colBB.Width()-borderWidth, colBB.Height()-borderWidth)
FillRect(buf, r, td.BackgroundCol)
FillRect(w, r, td.BackgroundCol)
} else if td.ShowBorder {
SetLineWidth(buf, borderWidth)
SetStrokeColor(buf, td.BorderCol)
SetLineWidth(w, borderWidth)
SetStrokeColor(w, td.BorderCol)
r := RectForWidthAndHeight(colBB.LL.X+borderWidth/2, colBB.LL.Y+borderWidth/2, colBB.Width()-borderWidth, colBB.Height()-borderWidth)
DrawRect(buf, r)
DrawRect(w, r)
}
}
func renderText(buf *bytes.Buffer, lines []string, td TextDescriptor, x, y float64, fontName string, fontSize int) {
func renderText(w io.Writer, lines []string, td TextDescriptor, x, y float64, fontName string, fontSize int) {
lh := font.LineHeight(fontName, fontSize)
for _, s := range lines {
if td.HAlign != AlignJustify {
@ -697,16 +723,16 @@ func renderText(buf *bytes.Buffer, lines []string, td TextDescriptor, x, y float
lineBB.Translate(-dx, 0)
if td.ShowLineBB {
// Draw line bounding box.
SetStrokeColor(buf, Black)
DrawRect(buf, lineBB)
SetStrokeColor(w, Black)
DrawRect(w, lineBB)
}
writeStringToBuf(buf, s, x-dx, y, td.StrokeCol, td.FillCol, td.RMode, fontName)
writeStringToBuf(w, s, x-dx, y, td.StrokeCol, td.FillCol, td.RMode, fontName)
y -= lh
continue
}
if len(s) > 0 {
flushJustifiedStringToBuf(buf, s, x, y, td.StrokeCol, td.FillCol, td.RMode)
flushJustifiedStringToBuf(w, s, x, y, td.StrokeCol, td.FillCol, td.RMode)
}
y -= lh
}
@ -715,7 +741,7 @@ func renderText(buf *bytes.Buffer, lines []string, td TextDescriptor, x, y float
// WriteColumn writes a text column using s at position x/y using a certain font, fontsize and a desired horizontal and vertical alignment.
// Enforce a desired column width by supplying a width > 0 (especially useful for justified text).
// It returns the bounding box of this column.
func WriteColumn(buf *bytes.Buffer, mediaBox, region *Rectangle, td TextDescriptor, width float64) *Rectangle {
func WriteColumn(w io.Writer, mediaBox, region *Rectangle, td TextDescriptor, width float64) *Rectangle {
x, y, dx, dy := td.X, td.Y, td.Dx, td.Dy
mTop, mBot, mLeft, mRight := td.MTop, td.MBot, td.MLeft, td.MRight
s, fontSize, borderWidth := td.Text, td.FontSize, td.BorderWidth
@ -780,9 +806,9 @@ func WriteColumn(buf *bytes.Buffer, mediaBox, region *Rectangle, td TextDescript
td.Scale, td.ScaleAbs,
td.ParIndent, td.FontName, &fontSize, &lines)
setFont(buf, td.FontKey, float32(fontSize))
setFont(w, td.FontKey, float32(fontSize))
m := calcRotateTransformMatrix(td.Rotation, x, y, colBB)
fmt.Fprintf(buf, "q %.2f %.2f %.2f %.2f %.2f %.2f cm ", m[0][0], m[0][1], m[1][0], m[1][1], m[2][0], m[2][1])
fmt.Fprintf(w, "q %.2f %.2f %.2f %.2f %.2f %.2f cm ", m[0][0], m[0][1], m[1][0], m[1][1], m[2][0], m[2][1])
x -= colBB.LL.X
y -= colBB.LL.Y
@ -790,21 +816,21 @@ func WriteColumn(buf *bytes.Buffer, mediaBox, region *Rectangle, td TextDescript
// Render background and border.
if td.ShowTextBB {
renderBackgroundAndBorder(buf, td, borderWidth, colBB)
renderBackgroundAndBorder(w, td, borderWidth, colBB)
}
// Render margins.
if td.ShowMargins {
drawMargins(buf, LightGray, colBB, borderWidth, mLeft, mRight, mTop, mBot)
drawMargins(w, LightGray, colBB, borderWidth, mLeft, mRight, mTop, mBot)
}
// Render text.
renderText(buf, lines, td, x, y, td.FontName, fontSize)
renderText(w, lines, td, x, y, td.FontName, fontSize)
buf.WriteString("Q ")
fmt.Fprintf(w, "Q ")
if td.HairCross {
DrawHairCross(buf, x0, y0, r)
DrawHairCross(w, x0, y0, r)
}
return colBB
@ -812,8 +838,8 @@ func WriteColumn(buf *bytes.Buffer, mediaBox, region *Rectangle, td TextDescript
// WriteMultiLine writes s at position x/y using a certain font, fontsize and a desired horizontal and vertical alignment.
// It returns the bounding box of this text column.
func WriteMultiLine(buf *bytes.Buffer, mediaBox, region *Rectangle, td TextDescriptor) *Rectangle {
return WriteColumn(buf, mediaBox, region, td, 0)
func WriteMultiLine(w io.Writer, mediaBox, region *Rectangle, td TextDescriptor) *Rectangle {
return WriteColumn(w, mediaBox, region, td, 0)
}
func anchorPosAndAlign(a anchor, r *Rectangle) (x, y float64, hAlign HAlignment, vAlign VAlignment) {
@ -841,22 +867,22 @@ func anchorPosAndAlign(a anchor, r *Rectangle) (x, y float64, hAlign HAlignment,
}
// WriteMultiLineAnchored writes multiple lines with anchored position and returns its bounding box.
func WriteMultiLineAnchored(buf *bytes.Buffer, mediaBox, region *Rectangle, td TextDescriptor, a anchor) *Rectangle {
func WriteMultiLineAnchored(w io.Writer, mediaBox, region *Rectangle, td TextDescriptor, a anchor) *Rectangle {
r := mediaBox
if region != nil {
r = region
}
td.X, td.Y, td.HAlign, td.VAlign = anchorPosAndAlign(a, r)
return WriteMultiLine(buf, mediaBox, region, td)
return WriteMultiLine(w, mediaBox, region, td)
}
// WriteColumnAnchored writes a justified text column with anchored position and returns its bounding box.
func WriteColumnAnchored(buf *bytes.Buffer, mediaBox, region *Rectangle, td TextDescriptor, a anchor, width float64) *Rectangle {
func WriteColumnAnchored(w io.Writer, mediaBox, region *Rectangle, td TextDescriptor, a anchor, width float64) *Rectangle {
r := mediaBox
if region != nil {
r = region
}
td.HAlign = AlignJustify
td.X, td.Y, _, td.VAlign = anchorPosAndAlign(a, r)
return WriteColumn(buf, mediaBox, region, td, width)
return WriteColumn(w, mediaBox, region, td, width)
}

View File

@ -17,7 +17,6 @@ limitations under the License.
package pdfcpu
import (
"path"
"path/filepath"
"time"
)
@ -522,7 +521,7 @@ func createPopupAnnotation(xRefTable *XRefTable, pageIndRef IndirectRef, annotRe
func createFileAttachmentAnnotation(xRefTable *XRefTable, pageIndRef IndirectRef, annotRect Array) (*IndirectRef, error) {
// macOS starts up iTunes for FileAttachments.
// macOS starts up iTunes for audio file attachments.
fileName := testAudioFileWAV
@ -531,7 +530,7 @@ func createFileAttachmentAnnotation(xRefTable *XRefTable, pageIndRef IndirectRef
return nil, err
}
fn := path.Base(fileName)
fn := filepath.Base(fileName)
fileSpecDict, err := xRefTable.NewFileSpecDict(fn, encodeUTF16String(fn), "attached by pdfcpu", *ir)
if err != nil {
return nil, err
@ -571,7 +570,7 @@ func createFileSpecDict(xRefTable *XRefTable, fileName string) (Dict, error) {
if err != nil {
return nil, err
}
fn := path.Base(fileName)
fn := filepath.Base(fileName)
return xRefTable.NewFileSpecDict(fn, encodeUTF16String(fn), "attached by pdfcpu", *ir)
}
@ -993,7 +992,7 @@ func createEmbeddedGoToAction(xRefTable *XRefTable) (*IndirectRef, error) {
"T": Dict(
map[string]Object{
"R": Name("C"),
"N": StringLiteral(f),
"N": StringLiteral(filepath.Base(f)),
},
),
},
@ -1041,7 +1040,7 @@ func createLinkAnnotationDictWithLaunchAction(xRefTable *XRefTable, pageIndRef I
map[string]Object{
"Type": Name("Action"),
"S": Name("Launch"),
"F": StringLiteral(".\\/golang.pdf"), // e.g pdf, wav..
"F": StringLiteral("golang.pdf"),
"Win": Dict(
map[string]Object{
"F": StringLiteral("golang.pdf"),

View File

@ -6,6 +6,7 @@ It provides an API and a command line interface. Supported are all versions up t
The commands are:
attachments list, add, remove, extract embedded file attachments
booklet arrange pages onto larger sheets of paper to make a booklet or zine
boxes list, add, remove page boundaries for selected pages
changeopw change owner password
changeupw change user password

60
vendor/github.com/pdfcpu/pdfcpu/pkg/pdfcpu/matrix.go generated vendored Normal file
View File

@ -0,0 +1,60 @@
/*
Copyright 2018 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 pdfcpu
import "fmt"
type matrix [3][3]float64
var identMatrix = matrix{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}
func (m matrix) multiply(n matrix) matrix {
var p matrix
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
for k := 0; k < 3; k++ {
p[i][j] += m[i][k] * n[k][j]
}
}
}
return p
}
func (m matrix) String() string {
return fmt.Sprintf("%3.2f %3.2f %3.2f\n%3.2f %3.2f %3.2f\n%3.2f %3.2f %3.2f\n",
m[0][0], m[0][1], m[0][2],
m[1][0], m[1][1], m[1][2],
m[2][0], m[2][1], m[2][2])
}
func calcTransformMatrix(sx, sy, sin, cos, dx, dy float64) matrix {
// Scale
m1 := identMatrix
m1[0][0] = sx
m1[1][1] = sy
// Rotate
m2 := identMatrix
m2[0][0] = cos
m2[0][1] = sin
m2[1][0] = -sin
m2[1][1] = cos
// Translate
m3 := identMatrix
m3[2][0] = dx
m3[2][1] = dy
return m1.multiply(m2).multiply(m3)
}

View File

@ -31,10 +31,9 @@ import (
)
var (
errInvalidGridID = errors.New("pdfcpu: nup: n: one of 2, 3, 4, 6, 8, 9, 12, 16")
errInvalidGridDims = errors.New("pdfcpu: grid: dimensions: m >= 0, n >= 0")
errInvalidNUpConfig = errors.New("pdfcpu: nup: invalid configuration string. Please consult pdfcpu help nup")
errInvalidGridConfig = errors.New("pdfcpu: nup: invalid configuration string. Please consult pdfcpu help grid")
errInvalidGridID = errors.New("pdfcpu nup: n must be one of 2, 3, 4, 6, 8, 9, 12, 16")
errInvalidGridDims = errors.New("pdfcpu grid: dimensions: m >= 0, n >= 0")
errInvalidNUpConfig = errors.New("pdfcpu: invalid configuration string")
)
var (
@ -54,12 +53,15 @@ var (
type nUpParamMap map[string]func(string, *NUp) error
var nupParamMap = nUpParamMap{
"dimensions": parseDimensionsNUp,
"formsize": parsePageFormatNUp,
"papersize": parsePageFormatNUp,
"orientation": parseOrientation,
"border": parseElementBorder,
"margin": parseElementMargin,
"dimensions": parseDimensionsNUp,
"formsize": parsePageFormatNUp,
"papersize": parsePageFormatNUp,
"orientation": parseOrientation,
"border": parseElementBorder,
"margin": parseElementMargin,
"backgroundcolor": parseSheetBackgroundColor,
"bgcolor": parseSheetBackgroundColor,
"guides": parseBookletGuides,
}
// Handle applies parameter completion and if successful
@ -87,16 +89,18 @@ func (m nUpParamMap) Handle(paramPrefix, paramValueStr string, nup *NUp) error {
// NUp represents the command details for the command "NUp".
type NUp struct {
PageDim *Dim // Page dimensions in display unit.
PageSize string // Paper size eg. A4L, A4P, A4(=default=A4P), see paperSize.go
UserDim bool // true if one of dimensions or paperSize provided overriding the default.
Orient orientation // One of rd(=default),dr,ld,dl
Grid *Dim // Intra page grid dimensions eg (2,2)
PageGrid bool // Create a mxn grid of pages for PDF inputfiles only (think "extra page n-Up").
ImgInputFile bool // Process image or PDF input files.
Margin int // Cropbox for n-Up content.
Border bool // Draw bounding box.
InpUnit DisplayUnit // input display unit.
PageDim *Dim // Page dimensions in display unit.
PageSize string // Paper size eg. A4L, A4P, A4(=default=A4P), see paperSize.go
UserDim bool // true if one of dimensions or paperSize provided overriding the default.
Orient orientation // One of rd(=default),dr,ld,dl
Grid *Dim // Intra page grid dimensions eg (2,2)
PageGrid bool // Create a mxn grid of pages for PDF inputfiles only (think "extra page n-Up").
ImgInputFile bool // Process image or PDF input files.
Margin int // Cropbox for n-Up content.
Border bool // Draw bounding box.
BookletGuides bool // Draw folding and cutting lines
InpUnit DisplayUnit // input display unit.
BgColor *SimpleColor // background color
}
// DefaultNUpConfig returns the default NUp configuration.
@ -114,6 +118,11 @@ func (nup NUp) String() string {
nup.PageSize, *nup.PageDim, nup.Orient, *nup.Grid, nup.PageGrid, nup.ImgInputFile)
}
// N returns the nUp value.
func (nup NUp) N() int {
return int(nup.Grid.Height * nup.Grid.Width)
}
type orientation int
func (o orientation) String() string {
@ -192,6 +201,19 @@ func parseElementBorder(s string, nup *NUp) error {
return nil
}
func parseBookletGuides(s string, nup *NUp) error {
switch strings.ToLower(s) {
case "on", "true":
nup.BookletGuides = true
case "off", "false":
nup.BookletGuides = false
default:
return errors.New("pdfcpu: booklet guides, please provide one of: on/off true/false")
}
return nil
}
func parseElementMargin(s string, nup *NUp) error {
f, err := strconv.ParseFloat(s, 64)
if err != nil {
@ -207,15 +229,19 @@ func parseElementMargin(s string, nup *NUp) error {
return nil
}
func parseSheetBackgroundColor(s string, nup *NUp) error {
c, err := parseColor(s)
if err != nil {
return err
}
nup.BgColor = &c
return nil
}
// ParseNUpDetails parses a NUp command string into an internal structure.
func ParseNUpDetails(s string, nup *NUp) error {
err1 := errInvalidNUpConfig
if nup.PageGrid {
err1 = errInvalidGridConfig
}
if s == "" {
return err1
return errInvalidNUpConfig
}
ss := strings.Split(s, ",")
@ -224,7 +250,7 @@ func ParseNUpDetails(s string, nup *NUp) error {
ss1 := strings.Split(s, ":")
if len(ss1) != 2 {
return err1
return errInvalidNUpConfig
}
paramPrefix := strings.TrimSpace(ss1[0])
@ -379,94 +405,159 @@ func rectsForGrid(nup *NUp) []*Rectangle {
return rr
}
// Calculate the matrix for transforming rectangle r1 with lower left corner in the origin into rectangle r2.
func calcTransMatrixForRect(r1, r2 *Rectangle, image bool) matrix {
var (
w, h float64
dx, dy float64
rot float64
)
if r2.Landscape() && r1.Portrait() || r2.Portrait() && r1.Landscape() {
rot = 90
r1.UR.X, r1.UR.Y = r1.UR.Y, r1.UR.X
func bestFitRectIntoRect(rSrc, rDest *Rectangle) (w, h, dx, dy, rot float64) {
if rSrc.FitsWithin(rDest) {
// Translate rSrc into center of rDest without scaling.
w = rSrc.Width()
h = rSrc.Height()
dx = rDest.Width()/2 - rSrc.Width()/2
dy = rDest.Height()/2 - rSrc.Height()/2
return
}
if r1.FitsWithin(r2) {
// Translate r1 into center of r2 w/o scaling up.
w = r1.Width()
h = r1.Height()
} else if r1.AspectRatio() <= r2.AspectRatio() {
// Scale down r1 height to fit into r2 height.
h = r2.Height()
w = r1.ScaledWidth(h)
} else {
// Scale down r1 width to fit into r2 width.
w = r2.Width()
h = r1.ScaledHeight(w)
}
dx = r2.LL.X - r1.LL.X*w/r1.Width() + r2.Width()/2 - w/2
dy = r2.LL.Y - r1.LL.Y*h/r1.Height() + r2.Height()/2 - h/2
if rot > 0 {
dx += w
if !image {
w /= r1.Width()
h /= r1.Height()
if rSrc.Landscape() {
if rDest.Landscape() {
if rSrc.AspectRatio() > rDest.AspectRatio() {
w = rDest.Width()
h = rSrc.ScaledHeight(w)
dy = (rDest.Height() - h) / 2
} else {
h = rDest.Height()
w = rSrc.ScaledWidth(h)
dx = (rDest.Width() - w) / 2
}
} else {
rot = 90
if 1/rSrc.AspectRatio() < rDest.AspectRatio() {
w = rDest.Height()
h = rSrc.ScaledHeight(w)
dx = (rDest.Width() - h) / 2
} else {
h = rDest.Width()
w = rSrc.ScaledWidth(h)
dy = (rDest.Height() - w) / 2
}
}
w, h = h, w
} else if !image {
w /= r1.Width()
h /= r1.Height()
return
}
// Scale
m1 := identMatrix
m1[0][0] = w
m1[1][1] = h
if rSrc.Portrait() {
if rDest.Portrait() {
if rSrc.AspectRatio() < rDest.AspectRatio() {
h = rDest.Height()
w = rSrc.ScaledWidth(h)
dx = (rDest.Width() - w) / 2
} else {
w = rDest.Width()
h = rSrc.ScaledHeight(w)
dy = (rDest.Height() - h) / 2
}
} else {
rot = 90
if 1/rSrc.AspectRatio() > rDest.AspectRatio() {
h = rDest.Width()
w = rSrc.ScaledWidth(h)
dy = (rDest.Height() - w) / 2
} else {
w = rDest.Height()
h = rSrc.ScaledHeight(w)
dx = (rDest.Width() - h) / 2
}
}
return
}
// Rotate
m2 := identMatrix
sin := math.Sin(float64(rot) * float64(degToRad))
cos := math.Cos(float64(rot) * float64(degToRad))
m2[0][0] = cos
m2[0][1] = sin
m2[1][0] = -sin
m2[1][1] = cos
w = rDest.Height()
if rDest.Portrait() {
w = rDest.Width()
}
h = w
dx = rDest.Width()/2 - rSrc.Width()/2
dy = rDest.Height()/2 - rSrc.Height()/2
// Translate
m3 := identMatrix
m3[2][0] = dx
m3[2][1] = dy
return m1.multiply(m2).multiply(m3)
return
}
func nUpTilePDFBytes(wr io.Writer, r1, r2 *Rectangle, formResID string, nup *NUp) {
func nUpTilePDFBytes(wr io.Writer, rSrc, rDest *Rectangle, formResID string, nup *NUp, rotate bool) {
// rScr is a rectangular region represented by form formResID in form space.
// rDest is an arbitrary rectangular region in dest space.
// It is the location where we want the form content to get rendered on a "best fit" basis.
// Accounting for the aspect ratios of rSrc and rDest "best fit" tries to fit the largest version of rScr into rDest.
// This may result in a 90 degree rotation.
// rotate indicates if we need to apply a post rotation of 180 degrees eg for booklets.
// Draw bounding box.
if nup.Border {
fmt.Fprintf(wr, "[]0 d 0.1 w %.2f %.2f m %.2f %.2f l %.2f %.2f l %.2f %.2f l s ",
r2.LL.X, r2.LL.Y, r2.UR.X, r2.LL.Y, r2.UR.X, r2.UR.Y, r2.LL.X, r2.UR.Y,
rDest.LL.X, rDest.LL.Y, rDest.UR.X, rDest.LL.Y, rDest.UR.X, rDest.UR.Y, rDest.LL.X, rDest.UR.Y,
)
}
// Apply margin.
croppedRect := r2.CroppedCopy(float64(nup.Margin))
// Apply margin to rDest which potentially makes it smaller.
rDestCr := rDest.CroppedCopy(float64(nup.Margin))
m := calcTransMatrixForRect(r1, croppedRect, nup.ImgInputFile)
// Calculate transform matrix.
// Best fit translation of a source rectangle into a destination rectangle.
w, h, dx, dy, r := bestFitRectIntoRect(rSrc, rDestCr)
if nup.BgColor != nil {
if nup.ImgInputFile {
// Fill background.
FillRectStacked(wr, rDest, *nup.BgColor)
} else if nup.Margin > 0 {
// Fill margins.
m := float64(nup.Margin)
drawMargins(wr, *nup.BgColor, rDest, 0, m, m, m, m)
}
}
// Apply additional rotation.
if rotate {
r += 180
}
sx := w
sy := h
if !nup.ImgInputFile {
sx /= rSrc.Width()
sy /= rSrc.Height()
}
sin := math.Sin(r * float64(degToRad))
cos := math.Cos(r * float64(degToRad))
switch r {
case 90:
dx += h
case 180:
dx += w
dy += h
case 270:
dy += w
}
dx += rDestCr.LL.X
dy += rDestCr.LL.Y
m := calcTransformMatrix(sx, sy, sin, cos, dx, dy)
// Apply transform matrix and display form.
fmt.Fprintf(wr, "q %.2f %.2f %.2f %.2f %.2f %.2f cm /%s Do Q ",
m[0][0], m[0][1], m[1][0], m[1][1], m[2][0], m[2][1], formResID)
}
func nUpImagePDFBytes(wr io.Writer, imgWidth, imgHeight int, nup *NUp, formResID string) {
func nUpImagePDFBytes(w io.Writer, imgWidth, imgHeight int, nup *NUp, formResID string) {
for _, r := range rectsForGrid(nup) {
nUpTilePDFBytes(wr, RectForDim(float64(imgWidth), float64(imgHeight)), r, formResID, nup)
// Append to content stream.
nUpTilePDFBytes(w, RectForDim(float64(imgWidth), float64(imgHeight)), r, formResID, nup, false)
}
}
func createNUpForm(xRefTable *XRefTable, imgIndRef *IndirectRef, w, h, i int) (*IndirectRef, error) {
func createNUpFormForImage(xRefTable *XRefTable, imgIndRef *IndirectRef, w, h, i int) (*IndirectRef, error) {
imgResID := fmt.Sprintf("Im%d", i)
bb := RectForDim(float64(w), float64(h))
@ -508,14 +599,14 @@ func createNUpForm(xRefTable *XRefTable, imgIndRef *IndirectRef, w, h, i int) (*
return xRefTable.IndRefForNewObject(sd)
}
func createNUpFormForPDFResource(xRefTable *XRefTable, resDict *IndirectRef, content []byte, cropBox *Rectangle) (*IndirectRef, error) {
func createNUpFormForPDF(xRefTable *XRefTable, resDict *IndirectRef, content []byte, cropBox *Rectangle) (*IndirectRef, error) {
sd := StreamDict{
Dict: Dict(
map[string]Object{
"Type": Name("XObject"),
"Subtype": Name("Form"),
"BBox": cropBox.Array(),
"Matrix": NewIntegerArray(1, 0, 0, 1, 0, 0),
"Matrix": NewNumberArray(1, 0, 0, 1, -cropBox.LL.X, -cropBox.LL.Y),
"Resources": *resDict,
},
),
@ -548,7 +639,7 @@ func NewNUpPageForImage(xRefTable *XRefTable, fileName string, parentIndRef *Ind
resID := 0
formIndRef, err := createNUpForm(xRefTable, imgIndRef, w, h, resID)
formIndRef, err := createNUpFormForImage(xRefTable, imgIndRef, w, h, resID)
if err != nil {
return nil, err
}
@ -578,7 +669,6 @@ func NewNUpPageForImage(xRefTable *XRefTable, fileName string, parentIndRef *Ind
return nil, err
}
// mediabox = physical page dimensions
dim := nup.PageDim
mediaBox := RectForDim(dim.Width, dim.Height)
@ -614,12 +704,27 @@ func NUpFromOneImage(ctx *Context, fileName string, nup *NUp, pagesDict Dict, pa
func wrapUpPage(ctx *Context, nup *NUp, d Dict, buf bytes.Buffer, pagesDict Dict, pagesIndRef *IndirectRef) error {
xRefTable := ctx.XRefTable
var fm FontMap
if nup.BookletGuides {
// For booklets only.
fm = drawBookletGuides(nup, &buf)
}
resourceDict := Dict(
map[string]Object{
"XObject": d,
},
)
fontRes, err := fontResources(xRefTable, fm)
if err != nil {
return err
}
if len(fontRes) > 0 {
resourceDict["Font"] = fontRes
}
resIndRef, err := xRefTable.IndRefForNewObject(resourceDict)
if err != nil {
return err
@ -635,7 +740,6 @@ func wrapUpPage(ctx *Context, nup *NUp, d Dict, buf bytes.Buffer, pagesDict Dict
return err
}
// mediabox = physical page dimensions
dim := nup.PageDim
mediaBox := RectForDim(dim.Width, dim.Height)
@ -675,19 +779,39 @@ func NUpFromMultipleImages(ctx *Context, fileNames []string, nup *NUp, pagesDict
var buf bytes.Buffer
rr := rectsForGrid(nup)
for i, fileName := range fileNames {
// fileCount must be a multiple of n.
// If not, we will insert blank pages at the end.
fileCount := len(fileNames)
if fileCount%nup.N() != 0 {
fileCount += nup.N() - fileCount%nup.N()
}
for i := 0; i < fileCount; i++ {
if i > 0 && i%len(rr) == 0 {
// Wrap complete nUp page.
if err := wrapUpPage(ctx, nup, formsResDict, buf, pagesDict, pagesIndRef); err != nil {
return err
}
buf.Reset()
formsResDict = NewDict()
}
rDest := rr[i%len(rr)]
var fileName string
if i < len(fileNames) {
fileName = fileNames[i]
}
if fileName == "" {
// This is an empty page at the end.
if nup.BgColor != nil {
FillRectStacked(&buf, rDest, *nup.BgColor)
}
continue
}
f, err := os.Open(fileName)
if err != nil {
return err
@ -702,7 +826,7 @@ func NUpFromMultipleImages(ctx *Context, fileNames []string, nup *NUp, pagesDict
return err
}
formIndRef, err := createNUpForm(xRefTable, imgIndRef, w, h, i)
formIndRef, err := createNUpFormForImage(xRefTable, imgIndRef, w, h, i)
if err != nil {
return err
}
@ -710,14 +834,15 @@ func NUpFromMultipleImages(ctx *Context, fileNames []string, nup *NUp, pagesDict
formResID := fmt.Sprintf("Fm%d", i)
formsResDict.Insert(formResID, *formIndRef)
nUpTilePDFBytes(&buf, RectForDim(float64(w), float64(h)), rr[i%len(rr)], formResID, nup)
// Append to content stream of page i.
nUpTilePDFBytes(&buf, RectForDim(float64(w), float64(h)), rr[i%len(rr)], formResID, nup, false)
}
// Wrap incomplete nUp page.
return wrapUpPage(ctx, nup, formsResDict, buf, pagesDict, pagesIndRef)
}
func sortedSelectedPages(pages IntSet) []int {
func sortSelectedPages(pages IntSet) []int {
var pageNumbers []int
for k, v := range pages {
if v {
@ -728,27 +853,52 @@ func sortedSelectedPages(pages IntSet) []int {
return pageNumbers
}
func nupPageNumber(i int, sortedPageNumbers []int) int {
var pageNumber int
if i < len(sortedPageNumbers) {
pageNumber = sortedPageNumbers[i]
}
return pageNumber
}
func (ctx *Context) nupPages(selectedPages IntSet, nup *NUp, pagesDict Dict, pagesIndRef *IndirectRef) error {
var buf bytes.Buffer
xRefTable := ctx.XRefTable
formsResDict := NewDict()
rr := rectsForGrid(nup)
for i, p := range sortedSelectedPages(selectedPages) {
sortedPageNumbers := sortSelectedPages(selectedPages)
pageCount := len(sortedPageNumbers)
// pageCount must be a multiple of n.
// If not, we will insert blank pages at the end.
if pageCount%nup.N() != 0 {
pageCount += nup.N() - pageCount%nup.N()
}
for i := 0; i < pageCount; i++ {
if i > 0 && i%len(rr) == 0 {
// Wrap complete nUp page.
if err := wrapUpPage(ctx, nup, formsResDict, buf, pagesDict, pagesIndRef); err != nil {
return err
}
buf.Reset()
formsResDict = NewDict()
}
rDest := rr[i%len(rr)]
pageNumber := nupPageNumber(i, sortedPageNumbers)
if pageNumber == 0 {
// This is an empty page at the end of a booklet.
if nup.BgColor != nil {
FillRectStacked(&buf, rDest, *nup.BgColor)
}
continue
}
consolidateRes := true
d, inhPAttrs, err := ctx.PageDict(p, consolidateRes)
d, inhPAttrs, err := ctx.PageDict(pageNumber, consolidateRes)
if err != nil {
return err
}
@ -775,7 +925,7 @@ func (ctx *Context) nupPages(selectedPages IntSet, nup *NUp, pagesDict Dict, pag
if inhPAttrs.cropBox != nil {
cropBox = inhPAttrs.cropBox
}
formIndRef, err := createNUpFormForPDFResource(xRefTable, ir, bb, cropBox)
formIndRef, err := createNUpFormForPDF(xRefTable, ir, bb, cropBox)
if err != nil {
return err
}
@ -783,8 +933,8 @@ func (ctx *Context) nupPages(selectedPages IntSet, nup *NUp, pagesDict Dict, pag
formResID := fmt.Sprintf("Fm%d", i)
formsResDict.Insert(formResID, *formIndRef)
// inhPAttrs.mediaBox
nUpTilePDFBytes(&buf, cropBox, rr[i%len(rr)], formResID, nup)
// Append to content stream of page i.
nUpTilePDFBytes(&buf, cropBox, rDest, formResID, nup, false)
}
// Wrap incomplete nUp page.

View File

@ -495,7 +495,7 @@ func parseName(line *string) (*Name, error) {
l = forwardParseBuf(l, 1)
// cut off on whitespace or delimiter
eok, _ := positionToNextWhitespaceOrChar(l, "/<>()[]")
eok, _ := positionToNextWhitespaceOrChar(l, "/<>()[]%")
if eok < 0 {
// Name terminated by eol.
*line = ""
@ -516,7 +516,7 @@ func parseName(line *string) (*Name, error) {
func processDictKeys(line *string, relaxed bool) (Dict, error) {
l := *line
eol := false
var eol bool
d := NewDict()
for !strings.HasPrefix(l, ">>") {
key, err := parseName(&l)
@ -621,7 +621,7 @@ func noBuf(l *string) bool {
}
func startParseNumericOrIndRef(l string) (string, string, int) {
i1, _ := positionToNextWhitespaceOrChar(l, "/<([]>")
i1, _ := positionToNextWhitespaceOrChar(l, "/<([]>%")
var l1 string
if i1 > 0 {
l1 = l[i1:]

View File

@ -828,7 +828,7 @@ func scanTrailerDictRemainder(s *bufio.Scanner, line string, buf bytes.Buffer) (
var i, j, k int
buf.WriteString(line)
buf.WriteString(" ")
buf.WriteString("\x0a")
log.Read.Printf("scanTrailer dictBuf after start tag: <%s>\n", line)
line = line[2:]
@ -841,7 +841,7 @@ func scanTrailerDictRemainder(s *bufio.Scanner, line string, buf bytes.Buffer) (
return "", err
}
buf.WriteString(line)
buf.WriteString(" ")
buf.WriteString("\x0a")
log.Read.Printf("scanTrailer dictBuf next line: <%s>\n", line)
}
@ -869,7 +869,7 @@ func scanTrailerDictRemainder(s *bufio.Scanner, line string, buf bytes.Buffer) (
return "", err
}
buf.WriteString(line)
buf.WriteString(" ")
buf.WriteString("\x0a")
log.Read.Printf("scanTrailer dictBuf next line: <%s>\n", line)
} else {
// Yes <<
@ -1091,13 +1091,14 @@ func bypassXrefSection(ctx *Context) error {
for {
line, err := scanLineRaw(s)
//println(line)
if err != nil {
break
}
if withinXref {
offset += int64(len(line) + eolCount)
if withinTrailer {
bb = append(bb, ' ')
bb = append(bb, '\n')
bb = append(bb, line...)
i := strings.Index(line, "startxref")
if i >= 0 {
@ -1662,6 +1663,14 @@ func object(ctx *Context, offset int64, objNr, genNr int) (o Object, endInd, str
log.Read.Printf("object %d: non matching objNr(%d) or generationNumber(%d) tags found.\n", objNr, *objectNr, *generationNr)
}
l = strings.TrimSpace(l)
if len(l) == 0 {
// 7.3.9
// Specifying the null object as the value of a dictionary entry (7.3.7, "Dictionary Objects")
// shall be equivalent to omitting the entry entirely.
return nil, endInd, streamInd, streamOffset, err
}
o, err = parseObject(&l)
return o, endInd, streamInd, streamOffset, err

View File

@ -121,29 +121,6 @@ var wmParamMap = watermarkParamMap{
"strokecolor": parseStrokeColor,
}
type matrix [3][3]float64
var identMatrix = matrix{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}
func (m matrix) multiply(n matrix) matrix {
var p matrix
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
for k := 0; k < 3; k++ {
p[i][j] += m[i][k] * n[k][j]
}
}
}
return p
}
func (m matrix) String() string {
return fmt.Sprintf("%3.2f %3.2f %3.2f\n%3.2f %3.2f %3.2f\n%3.2f %3.2f %3.2f\n",
m[0][0], m[0][1], m[0][2],
m[1][0], m[1][1], m[1][2],
m[2][0], m[2][1], m[2][2])
}
// SimpleColor is a simple rgb wrapper.
type SimpleColor struct {
R, G, B float32 // intensities between 0 and 1.
@ -205,7 +182,7 @@ type Watermark struct {
Rotation float64 // rotation to apply in degrees. -180 <= x <= 180
Diagonal int // paint along the diagonal.
UserRotOrDiagonal bool // true if one of rotation or diagonal provided overriding the default.
Opacity float64 // opacity the watermark. 0 <= x <= 1
Opacity float64 // opacity of the watermark. 0 <= x <= 1
RenderMode RenderMode // fill=0, stroke=1 fill&stroke=2
Scale float64 // relative scale factor: 0 <= x <= 1, absolute scale factor: 0 <= x
ScaleEff float64 // effective scale factor
@ -846,7 +823,7 @@ func (wm *Watermark) calcBoundingBox(pageNr int) {
return
}
func (wm *Watermark) calcTransformMatrix() *matrix {
func (wm *Watermark) calcTransformMatrix() matrix {
var sin, cos float64
r := wm.Rotation
@ -868,25 +845,16 @@ func (wm *Watermark) calcTransformMatrix() *matrix {
sin = math.Sin(float64(r) * float64(degToRad))
cos = math.Cos(float64(r) * float64(degToRad))
// 1) Rotate
m1 := identMatrix
m1[0][0] = cos
m1[0][1] = sin
m1[1][0] = -sin
m1[1][1] = cos
// 2) Translate
m2 := identMatrix
var dy float64
if !wm.isImage() && !wm.isPDF() {
dy = wm.bb.LL.Y
}
ll := lowerLeftCorner(wm.vp.Width(), wm.vp.Height(), wm.bb.Width(), wm.bb.Height(), wm.Pos)
m2[2][0] = ll.X + wm.bb.Width()/2 + float64(wm.Dx) + sin*(wm.bb.Height()/2+dy) - cos*wm.bb.Width()/2
m2[2][1] = ll.Y + wm.bb.Height()/2 + float64(wm.Dy) - cos*(wm.bb.Height()/2+dy) - sin*wm.bb.Width()/2
m := m1.multiply(m2)
return &m
dx := ll.X + wm.bb.Width()/2 + float64(wm.Dx) + sin*(wm.bb.Height()/2+dy) - cos*wm.bb.Width()/2
dy = ll.Y + wm.bb.Height()/2 + float64(wm.Dy) - cos*(wm.bb.Height()/2+dy) - sin*wm.bb.Width()/2
return calcTransformMatrix(1, 1, sin, cos, dx, dy)
}
func onTopString(onTop bool) string {

View File

@ -124,7 +124,7 @@ func validateNames(xRefTable *pdf.XRefTable, rootDict pdf.Dict, required bool, s
if err != nil {
return err
}
if d == nil {
if d == nil || len(d) == 0 {
continue
}

View File

@ -23,7 +23,7 @@ import (
)
// VersionStr is the current pdfcpu version.
var VersionStr = "v0.3.8 dev"
var VersionStr = "v0.3.9 dev"
// Version is a type for the internal representation of PDF versions.
type Version int

View File

@ -769,10 +769,6 @@ func writeXRefStream(ctx *Context) error {
w := ctx.Write
if err = w.WriteEol(); err != nil {
return err
}
if _, err = w.WriteString("startxref"); err != nil {
return err
}

View File

@ -404,7 +404,7 @@ func writeStream(w *WriteContext, sd StreamDict) (int64, error) {
return 0, errors.Errorf("writeStream: failed to write raw content: %d bytes written - streamlength:%d", c, *sd.StreamLength)
}
e, err := w.WriteString("endstream")
e, err := w.WriteString(fmt.Sprintf("%sendstream", w.Eol))
if err != nil {
return 0, errors.Wrapf(err, "writeStream: failed to write raw content")
}

6
vendor/modules.txt vendored
View File

@ -120,6 +120,8 @@ github.com/lightningnetwork/lnd/clock
github.com/lightningnetwork/lnd/queue
# github.com/lightningnetwork/lnd/ticker v1.0.0
github.com/lightningnetwork/lnd/ticker
# github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/logrusorgru/aurora
# github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796
github.com/ltcsuite/ltcd/chaincfg
github.com/ltcsuite/ltcd/chaincfg/chainhash
@ -128,7 +130,7 @@ github.com/ltcsuite/ltcd/wire
github.com/mattn/go-sqlite3
# github.com/miekg/dns v1.1.29
github.com/miekg/dns
# github.com/muun/libwallet v0.7.0
# github.com/muun/libwallet v0.8.0
github.com/muun/libwallet
github.com/muun/libwallet/addresses
github.com/muun/libwallet/aescbc
@ -141,7 +143,7 @@ github.com/muun/libwallet/recoverycode
github.com/muun/libwallet/sphinx
github.com/muun/libwallet/swaps
github.com/muun/libwallet/walletdb
# github.com/pdfcpu/pdfcpu v0.3.8
# github.com/pdfcpu/pdfcpu v0.3.9
github.com/pdfcpu/pdfcpu/internal/config
github.com/pdfcpu/pdfcpu/internal/corefont/metrics
github.com/pdfcpu/pdfcpu/pkg/api