From bc67c52d3adfa0c8252d6ee0a112ca112b5d2315 Mon Sep 17 00:00:00 2001 From: DocFast Bot Date: Sat, 21 Feb 2026 13:29:48 +0000 Subject: [PATCH] feat: add Go, PHP, and Laravel SDKs - Go SDK: zero deps, functional options pattern, full endpoint coverage - PHP SDK: PHP 8.1+, curl-based, PdfOptions class, PSR-4 autoloading - Laravel package: ServiceProvider, Facade, config publishing - All SDKs document complete PDF options including new v0.4.5 params --- sdk/go/README.md | 151 +++++++++++ sdk/go/docfast.go | 293 +++++++++++++++++++++ sdk/go/go.mod | 3 + sdk/laravel/README.md | 114 ++++++++ sdk/laravel/composer.json | 34 +++ sdk/laravel/config/docfast.php | 33 +++ sdk/laravel/src/DocFastServiceProvider.php | 34 +++ sdk/laravel/src/Facades/DocFast.php | 25 ++ sdk/php/README.md | 156 +++++++++++ sdk/php/composer.json | 24 ++ sdk/php/src/Client.php | 183 +++++++++++++ sdk/php/src/DocFastException.php | 18 ++ sdk/php/src/PdfOptions.php | 65 +++++ 13 files changed, 1133 insertions(+) create mode 100644 sdk/go/README.md create mode 100644 sdk/go/docfast.go create mode 100644 sdk/go/go.mod create mode 100644 sdk/laravel/README.md create mode 100644 sdk/laravel/composer.json create mode 100644 sdk/laravel/config/docfast.php create mode 100644 sdk/laravel/src/DocFastServiceProvider.php create mode 100644 sdk/laravel/src/Facades/DocFast.php create mode 100644 sdk/php/README.md create mode 100644 sdk/php/composer.json create mode 100644 sdk/php/src/Client.php create mode 100644 sdk/php/src/DocFastException.php create mode 100644 sdk/php/src/PdfOptions.php diff --git a/sdk/go/README.md b/sdk/go/README.md new file mode 100644 index 0000000..9102961 --- /dev/null +++ b/sdk/go/README.md @@ -0,0 +1,151 @@ +# DocFast Go SDK + +Official Go client for the [DocFast](https://docfast.dev) HTML/Markdown to PDF API. + +## Installation + +```bash +go get github.com/docfast/docfast-go +``` + +## Quick Start + +```go +package main + +import ( + "os" + docfast "github.com/docfast/docfast-go" +) + +func main() { + client := docfast.New("df_pro_your_api_key") + + // HTML to PDF + pdf, err := client.HTML("

Hello World

Generated with DocFast

", nil) + if err != nil { + panic(err) + } + os.WriteFile("output.pdf", pdf, 0644) +} +``` + +## Usage + +### HTML to PDF + +```go +pdf, err := client.HTML("

Hello

", &docfast.PDFOptions{ + Format: "A4", + Landscape: true, + Margin: &docfast.Margin{Top: "20mm", Bottom: "20mm"}, +}) +``` + +### HTML with custom CSS + +```go +pdf, err := client.HTMLWithCSS( + "

Styled

", + "h1 { color: navy; font-family: Georgia; }", + nil, +) +``` + +### Markdown to PDF + +```go +pdf, err := client.Markdown("# Report\n\nGenerated **automatically**.", nil) +``` + +### URL to PDF + +```go +pdf, err := client.URL("https://example.com", &docfast.PDFOptions{ + Format: "Letter", + PrintBackground: docfast.Bool(true), +}) +``` + +### Headers and Footers + +```go +pdf, err := client.HTML(html, &docfast.PDFOptions{ + DisplayHeaderFooter: true, + HeaderTemplate: `
My Document
`, + FooterTemplate: `
Page of
`, + Margin: &docfast.Margin{Top: "40mm", Bottom: "20mm"}, +}) +``` + +### Custom Page Size + +```go +pdf, err := client.HTML(html, &docfast.PDFOptions{ + Width: "8.5in", + Height: "11in", + Scale: 0.8, +}) +``` + +### Templates + +```go +// List available templates +templates, err := client.Templates() + +// Render a template +pdf, err := client.RenderTemplate("invoice", map[string]interface{}{ + "company": "Acme Corp", + "items": []map[string]interface{}{{"name": "Widget", "price": 9.99}}, + "_format": "A4", +}) +``` + +## PDF Options + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `Format` | string | `"A4"` | Page size: A4, Letter, Legal, A3, A5, Tabloid | +| `Landscape` | bool | `false` | Landscape orientation | +| `Margin` | *Margin | `nil` | Page margins (CSS units) | +| `PrintBackground` | *bool | `true` | Print background graphics | +| `Filename` | string | `""` | Suggested filename | +| `HeaderTemplate` | string | `""` | HTML header template | +| `FooterTemplate` | string | `""` | HTML footer template | +| `DisplayHeaderFooter` | bool | `false` | Show header/footer | +| `Scale` | float64 | `1` | Rendering scale (0.1–2.0) | +| `PageRanges` | string | `""` | Pages to print (e.g. "1-3,5") | +| `PreferCSSPageSize` | bool | `false` | Prefer CSS @page size | +| `Width` | string | `""` | Custom paper width | +| `Height` | string | `""` | Custom paper height | + +## Error Handling + +```go +pdf, err := client.HTML("

Test

", nil) +if err != nil { + var apiErr *docfast.Error + if errors.As(err, &apiErr) { + fmt.Printf("API error: %s (status %d)\n", apiErr.Message, apiErr.StatusCode) + } +} +``` + +## Configuration + +```go +// Custom base URL (e.g. for self-hosted or staging) +client := docfast.New("key", docfast.WithBaseURL("https://staging.docfast.dev")) + +// Custom HTTP client +client := docfast.New("key", docfast.WithHTTPClient(&http.Client{ + Timeout: 120 * time.Second, +})) +``` + +## Links + +- [Documentation](https://docfast.dev/docs) +- [API Reference](https://docfast.dev/openapi.json) +- [Get an API Key](https://docfast.dev) diff --git a/sdk/go/docfast.go b/sdk/go/docfast.go new file mode 100644 index 0000000..2491474 --- /dev/null +++ b/sdk/go/docfast.go @@ -0,0 +1,293 @@ +// Package docfast provides a Go client for the DocFast HTML/Markdown to PDF API. +// +// Usage: +// +// client := docfast.New("df_pro_your_key") +// pdf, err := client.HTML("

Hello

", nil) +package docfast + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "time" +) + +const defaultBaseURL = "https://docfast.dev" + +// Margin defines PDF page margins using CSS units (e.g. "20mm", "1in"). +type Margin struct { + Top string `json:"top,omitempty"` + Bottom string `json:"bottom,omitempty"` + Left string `json:"left,omitempty"` + Right string `json:"right,omitempty"` +} + +// PDFOptions configures PDF generation. All fields are optional. +type PDFOptions struct { + // Page size: A4, Letter, Legal, A3, A5, Tabloid. Ignored if Width/Height set. + Format string `json:"format,omitempty"` + // Landscape orientation. + Landscape bool `json:"landscape,omitempty"` + // Page margins. + Margin *Margin `json:"margin,omitempty"` + // Print background graphics and colors. Default: true. + PrintBackground *bool `json:"printBackground,omitempty"` + // Suggested filename for the PDF download. + Filename string `json:"filename,omitempty"` + // HTML template for page header. Requires DisplayHeaderFooter: true. + // Supports CSS classes: date, title, url, pageNumber, totalPages. + HeaderTemplate string `json:"headerTemplate,omitempty"` + // HTML template for page footer. Same classes as HeaderTemplate. + FooterTemplate string `json:"footerTemplate,omitempty"` + // Show header and footer templates. + DisplayHeaderFooter bool `json:"displayHeaderFooter,omitempty"` + // Scale of webpage rendering (0.1 to 2.0). Default: 1. + Scale float64 `json:"scale,omitempty"` + // Paper ranges to print, e.g. "1-3,5". + PageRanges string `json:"pageRanges,omitempty"` + // Give CSS @page size priority over Format. + PreferCSSPageSize bool `json:"preferCSSPageSize,omitempty"` + // Paper width with units (e.g. "8.5in"). Overrides Format. + Width string `json:"width,omitempty"` + // Paper height with units (e.g. "11in"). Overrides Format. + Height string `json:"height,omitempty"` +} + +// Template describes an available PDF template. +type Template struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description,omitempty"` +} + +// Error is returned when the API responds with an error. +type Error struct { + StatusCode int + Message string + Code string +} + +func (e *Error) Error() string { + if e.Code != "" { + return fmt.Sprintf("docfast: %s (code=%s, status=%d)", e.Message, e.Code, e.StatusCode) + } + return fmt.Sprintf("docfast: %s (status=%d)", e.Message, e.StatusCode) +} + +// ClientOption configures the Client. +type ClientOption func(*Client) + +// WithBaseURL sets a custom API base URL. +func WithBaseURL(url string) ClientOption { + return func(c *Client) { c.baseURL = url } +} + +// WithHTTPClient sets a custom http.Client. +func WithHTTPClient(hc *http.Client) ClientOption { + return func(c *Client) { c.httpClient = hc } +} + +// Client is the DocFast API client. +type Client struct { + apiKey string + baseURL string + httpClient *http.Client +} + +// New creates a new DocFast client. +func New(apiKey string, opts ...ClientOption) *Client { + c := &Client{ + apiKey: apiKey, + baseURL: defaultBaseURL, + httpClient: &http.Client{ + Timeout: 60 * time.Second, + }, + } + for _, o := range opts { + o(c) + } + return c +} + +func (c *Client) post(path string, body map[string]interface{}) ([]byte, error) { + data, err := json.Marshal(body) + if err != nil { + return nil, fmt.Errorf("docfast: marshal error: %w", err) + } + + req, err := http.NewRequest("POST", c.baseURL+path, bytes.NewReader(data)) + if err != nil { + return nil, fmt.Errorf("docfast: request error: %w", err) + } + req.Header.Set("Authorization", "Bearer "+c.apiKey) + req.Header.Set("Content-Type", "application/json") + + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("docfast: request failed: %w", err) + } + defer resp.Body.Close() + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("docfast: read error: %w", err) + } + + if resp.StatusCode >= 400 { + apiErr := &Error{StatusCode: resp.StatusCode, Message: fmt.Sprintf("HTTP %d", resp.StatusCode)} + var errResp struct { + Error string `json:"error"` + Code string `json:"code"` + } + if json.Unmarshal(respBody, &errResp) == nil { + if errResp.Error != "" { + apiErr.Message = errResp.Error + } + apiErr.Code = errResp.Code + } + return nil, apiErr + } + + return respBody, nil +} + +func (c *Client) get(path string) ([]byte, error) { + req, err := http.NewRequest("GET", c.baseURL+path, nil) + if err != nil { + return nil, fmt.Errorf("docfast: request error: %w", err) + } + req.Header.Set("Authorization", "Bearer "+c.apiKey) + + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("docfast: request failed: %w", err) + } + defer resp.Body.Close() + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("docfast: read error: %w", err) + } + + if resp.StatusCode >= 400 { + apiErr := &Error{StatusCode: resp.StatusCode, Message: fmt.Sprintf("HTTP %d", resp.StatusCode)} + var errResp struct { + Error string `json:"error"` + Code string `json:"code"` + } + if json.Unmarshal(respBody, &errResp) == nil && errResp.Error != "" { + apiErr.Message = errResp.Error + apiErr.Code = errResp.Code + } + return nil, apiErr + } + + return respBody, nil +} + +func mergeOptions(body map[string]interface{}, opts *PDFOptions) { + if opts == nil { + return + } + if opts.Format != "" { + body["format"] = opts.Format + } + if opts.Landscape { + body["landscape"] = true + } + if opts.Margin != nil { + body["margin"] = opts.Margin + } + if opts.PrintBackground != nil { + body["printBackground"] = *opts.PrintBackground + } + if opts.Filename != "" { + body["filename"] = opts.Filename + } + if opts.HeaderTemplate != "" { + body["headerTemplate"] = opts.HeaderTemplate + } + if opts.FooterTemplate != "" { + body["footerTemplate"] = opts.FooterTemplate + } + if opts.DisplayHeaderFooter { + body["displayHeaderFooter"] = true + } + if opts.Scale != 0 { + body["scale"] = opts.Scale + } + if opts.PageRanges != "" { + body["pageRanges"] = opts.PageRanges + } + if opts.PreferCSSPageSize { + body["preferCSSPageSize"] = true + } + if opts.Width != "" { + body["width"] = opts.Width + } + if opts.Height != "" { + body["height"] = opts.Height + } +} + +// HTML converts HTML content to PDF. Returns the raw PDF bytes. +func (c *Client) HTML(html string, opts *PDFOptions) ([]byte, error) { + body := map[string]interface{}{"html": html} + mergeOptions(body, opts) + return c.post("/v1/convert/html", body) +} + +// HTMLWithCSS converts an HTML fragment with optional CSS to PDF. +func (c *Client) HTMLWithCSS(html, css string, opts *PDFOptions) ([]byte, error) { + body := map[string]interface{}{"html": html, "css": css} + mergeOptions(body, opts) + return c.post("/v1/convert/html", body) +} + +// Markdown converts Markdown content to PDF. +func (c *Client) Markdown(markdown string, opts *PDFOptions) ([]byte, error) { + body := map[string]interface{}{"markdown": markdown} + mergeOptions(body, opts) + return c.post("/v1/convert/markdown", body) +} + +// MarkdownWithCSS converts Markdown with optional CSS to PDF. +func (c *Client) MarkdownWithCSS(markdown, css string, opts *PDFOptions) ([]byte, error) { + body := map[string]interface{}{"markdown": markdown, "css": css} + mergeOptions(body, opts) + return c.post("/v1/convert/markdown", body) +} + +// URL converts a web page at the given URL to PDF. +func (c *Client) URL(url string, opts *PDFOptions) ([]byte, error) { + body := map[string]interface{}{"url": url} + mergeOptions(body, opts) + return c.post("/v1/convert/url", body) +} + +// Templates returns the list of available PDF templates. +func (c *Client) Templates() ([]Template, error) { + data, err := c.get("/v1/templates") + if err != nil { + return nil, err + } + var result struct { + Templates []Template `json:"templates"` + } + if err := json.Unmarshal(data, &result); err != nil { + return nil, fmt.Errorf("docfast: decode error: %w", err) + } + return result.Templates, nil +} + +// RenderTemplate renders a template with the given data and returns PDF bytes. +func (c *Client) RenderTemplate(templateID string, data map[string]interface{}) ([]byte, error) { + body := map[string]interface{}{"data": data} + return c.post("/v1/templates/"+templateID, body) +} + +// Bool is a helper to create a *bool for PDFOptions.PrintBackground. +func Bool(v bool) *bool { return &v } diff --git a/sdk/go/go.mod b/sdk/go/go.mod new file mode 100644 index 0000000..3c69be7 --- /dev/null +++ b/sdk/go/go.mod @@ -0,0 +1,3 @@ +module github.com/docfast/docfast-go + +go 1.21 diff --git a/sdk/laravel/README.md b/sdk/laravel/README.md new file mode 100644 index 0000000..117d57a --- /dev/null +++ b/sdk/laravel/README.md @@ -0,0 +1,114 @@ +# DocFast for Laravel + +Official Laravel integration for the [DocFast](https://docfast.dev) HTML/Markdown to PDF API. + +## Installation + +```bash +composer require docfast/laravel +``` + +Add your API key to `.env`: + +```env +DOCFAST_API_KEY=df_pro_your_api_key +``` + +Publish the config (optional): + +```bash +php artisan vendor:publish --tag=docfast-config +``` + +## Usage + +### Via Facade + +```php +use DocFast\Laravel\Facades\DocFast; + +// HTML to PDF +$pdf = DocFast::html('

Invoice

Total: €99.00

'); +return response($pdf) + ->header('Content-Type', 'application/pdf') + ->header('Content-Disposition', 'inline; filename="invoice.pdf"'); +``` + +### Via Dependency Injection + +```php +use DocFast\Client; + +class InvoiceController extends Controller +{ + public function download(Client $docfast) + { + $pdf = $docfast->html(view('invoice')->render()); + return response($pdf) + ->header('Content-Type', 'application/pdf'); + } +} +``` + +### Markdown to PDF + +```php +$pdf = DocFast::markdown('# Report\n\nGenerated at ' . now()); +``` + +### URL to PDF + +```php +$pdf = DocFast::url('https://example.com'); +``` + +### With PDF Options + +```php +use DocFast\PdfOptions; + +$options = new PdfOptions(); +$options->format = 'Letter'; +$options->landscape = true; +$options->margin = ['top' => '20mm', 'bottom' => '20mm']; + +$pdf = DocFast::html($html, null, $options); +``` + +### Headers and Footers + +```php +$options = new PdfOptions(); +$options->displayHeaderFooter = true; +$options->footerTemplate = '
Page
'; +$options->margin = ['top' => '10mm', 'bottom' => '20mm']; + +$pdf = DocFast::html(view('report')->render(), null, $options); +``` + +### Templates + +```php +$pdf = DocFast::renderTemplate('invoice', [ + 'company' => 'Acme Corp', + 'items' => [['name' => 'Widget', 'price' => 9.99]], +]); +``` + +## Configuration + +```php +// config/docfast.php +return [ + 'api_key' => env('DOCFAST_API_KEY'), + 'base_url' => env('DOCFAST_BASE_URL', 'https://docfast.dev'), + 'timeout' => env('DOCFAST_TIMEOUT', 60), +]; +``` + +## Links + +- [PHP SDK](../php/) — standalone PHP client +- [Documentation](https://docfast.dev/docs) +- [API Reference](https://docfast.dev/openapi.json) +- [Get an API Key](https://docfast.dev) diff --git a/sdk/laravel/composer.json b/sdk/laravel/composer.json new file mode 100644 index 0000000..312f21d --- /dev/null +++ b/sdk/laravel/composer.json @@ -0,0 +1,34 @@ +{ + "name": "docfast/laravel", + "description": "Laravel integration for the DocFast HTML/Markdown to PDF API", + "type": "library", + "license": "MIT", + "homepage": "https://docfast.dev", + "keywords": ["pdf", "html-to-pdf", "laravel", "docfast"], + "require": { + "php": "^8.1", + "illuminate/support": "^10.0|^11.0|^12.0", + "docfast/docfast-php": "^1.0" + }, + "autoload": { + "psr-4": { + "DocFast\\Laravel\\": "src/" + } + }, + "extra": { + "laravel": { + "providers": [ + "DocFast\\Laravel\\DocFastServiceProvider" + ], + "aliases": { + "DocFast": "DocFast\\Laravel\\Facades\\DocFast" + } + } + }, + "authors": [ + { + "name": "DocFast", + "homepage": "https://docfast.dev" + } + ] +} diff --git a/sdk/laravel/config/docfast.php b/sdk/laravel/config/docfast.php new file mode 100644 index 0000000..440ed83 --- /dev/null +++ b/sdk/laravel/config/docfast.php @@ -0,0 +1,33 @@ + env('DOCFAST_API_KEY'), + + /* + |-------------------------------------------------------------------------- + | Base URL + |-------------------------------------------------------------------------- + | + | The DocFast API base URL. Change for staging or self-hosted instances. + | + */ + 'base_url' => env('DOCFAST_BASE_URL', 'https://docfast.dev'), + + /* + |-------------------------------------------------------------------------- + | Timeout + |-------------------------------------------------------------------------- + | + | Request timeout in seconds for PDF generation. + | + */ + 'timeout' => env('DOCFAST_TIMEOUT', 60), +]; diff --git a/sdk/laravel/src/DocFastServiceProvider.php b/sdk/laravel/src/DocFastServiceProvider.php new file mode 100644 index 0000000..6c0d3d5 --- /dev/null +++ b/sdk/laravel/src/DocFastServiceProvider.php @@ -0,0 +1,34 @@ +mergeConfigFrom(__DIR__ . '/../config/docfast.php', 'docfast'); + + $this->app->singleton(Client::class, function ($app) { + $config = $app['config']['docfast']; + return new Client( + $config['api_key'] ?? '', + $config['base_url'] ?? 'https://docfast.dev', + $config['timeout'] ?? 60, + ); + }); + + $this->app->alias(Client::class, 'docfast'); + } + + public function boot(): void + { + $this->publishes([ + __DIR__ . '/../config/docfast.php' => config_path('docfast.php'), + ], 'docfast-config'); + } +} diff --git a/sdk/laravel/src/Facades/DocFast.php b/sdk/laravel/src/Facades/DocFast.php new file mode 100644 index 0000000..99f895d --- /dev/null +++ b/sdk/laravel/src/Facades/DocFast.php @@ -0,0 +1,25 @@ +html('

Hello World

'); +file_put_contents('output.pdf', $pdf); +``` + +## Usage + +### HTML to PDF + +```php +$pdf = $client->html('

Hello

My document

'); +``` + +### HTML with CSS + +```php +$pdf = $client->html( + '

Styled

', + 'h1 { color: navy; font-family: Georgia; }' +); +``` + +### HTML with PDF Options + +```php +use DocFast\PdfOptions; + +$options = new PdfOptions(); +$options->format = 'Letter'; +$options->landscape = true; +$options->margin = ['top' => '20mm', 'bottom' => '20mm', 'left' => '15mm', 'right' => '15mm']; +$options->printBackground = true; + +$pdf = $client->html('

Report

', null, $options); +``` + +### Markdown to PDF + +```php +$pdf = $client->markdown('# Hello World\n\nThis is **bold** text.'); +``` + +### URL to PDF + +```php +$pdf = $client->url('https://example.com'); +``` + +### Headers and Footers + +```php +$options = new PdfOptions(); +$options->displayHeaderFooter = true; +$options->headerTemplate = '
My Document
'; +$options->footerTemplate = '
Page /
'; +$options->margin = ['top' => '40mm', 'bottom' => '20mm']; + +$pdf = $client->html($html, null, $options); +``` + +### Custom Page Size + +```php +$options = new PdfOptions(); +$options->width = '8.5in'; +$options->height = '11in'; +$options->scale = 0.8; + +$pdf = $client->html($html, null, $options); +``` + +### Templates + +```php +// List templates +$templates = $client->templates(); + +// Render a template +$pdf = $client->renderTemplate('invoice', [ + 'company' => 'Acme Corp', + 'items' => [['name' => 'Widget', 'price' => 9.99]], +]); +``` + +## PDF Options + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `format` | string | `"A4"` | Page size: A4, Letter, Legal, A3, A5, Tabloid | +| `landscape` | bool | `false` | Landscape orientation | +| `margin` | array | `null` | Margins with top/bottom/left/right keys (CSS units) | +| `printBackground` | bool | `true` | Print background graphics | +| `filename` | string | `null` | Suggested filename | +| `headerTemplate` | string | `null` | HTML header template | +| `footerTemplate` | string | `null` | HTML footer template | +| `displayHeaderFooter` | bool | `false` | Show header/footer | +| `scale` | float | `1` | Rendering scale (0.1–2.0) | +| `pageRanges` | string | `null` | Pages to print (e.g. "1-3,5") | +| `preferCSSPageSize` | bool | `false` | Prefer CSS @page size | +| `width` | string | `null` | Custom paper width | +| `height` | string | `null` | Custom paper height | + +## Error Handling + +```php +use DocFast\DocFastException; + +try { + $pdf = $client->html('

Test

'); +} catch (DocFastException $e) { + echo "Error: {$e->getMessage()} (status: {$e->statusCode})\n"; +} +``` + +## Configuration + +```php +// Custom base URL +$client = new Client('key', 'https://staging.docfast.dev'); + +// Custom timeout (seconds) +$client = new Client('key', 'https://docfast.dev', 120); +``` + +## Laravel Integration + +See the [DocFast Laravel package](../laravel/) for a dedicated Laravel integration with facades, config, and service provider. + +## Links + +- [Documentation](https://docfast.dev/docs) +- [API Reference](https://docfast.dev/openapi.json) +- [Get an API Key](https://docfast.dev) diff --git a/sdk/php/composer.json b/sdk/php/composer.json new file mode 100644 index 0000000..5277c34 --- /dev/null +++ b/sdk/php/composer.json @@ -0,0 +1,24 @@ +{ + "name": "docfast/docfast-php", + "description": "Official PHP SDK for the DocFast HTML/Markdown to PDF API", + "type": "library", + "license": "MIT", + "homepage": "https://docfast.dev", + "keywords": ["pdf", "html-to-pdf", "markdown-to-pdf", "api", "docfast"], + "require": { + "php": "^8.1", + "ext-json": "*", + "ext-curl": "*" + }, + "autoload": { + "psr-4": { + "DocFast\\": "src/" + } + }, + "authors": [ + { + "name": "DocFast", + "homepage": "https://docfast.dev" + } + ] +} diff --git a/sdk/php/src/Client.php b/sdk/php/src/Client.php new file mode 100644 index 0000000..bee8426 --- /dev/null +++ b/sdk/php/src/Client.php @@ -0,0 +1,183 @@ +apiKey = $apiKey; + $this->baseUrl = rtrim($baseUrl, '/'); + $this->timeout = $timeout; + } + + /** + * Convert HTML to PDF. + * + * @param string $html HTML content + * @param string|null $css Optional CSS to inject + * @param PdfOptions|null $options PDF options + * @return string Raw PDF bytes + * @throws DocFastException + */ + public function html(string $html, ?string $css = null, ?PdfOptions $options = null): string + { + $body = ['html' => $html]; + if ($css !== null) { + $body['css'] = $css; + } + return $this->convert('/v1/convert/html', $body, $options); + } + + /** + * Convert Markdown to PDF. + * + * @param string $markdown Markdown content + * @param string|null $css Optional CSS to inject + * @param PdfOptions|null $options PDF options + * @return string Raw PDF bytes + * @throws DocFastException + */ + public function markdown(string $markdown, ?string $css = null, ?PdfOptions $options = null): string + { + $body = ['markdown' => $markdown]; + if ($css !== null) { + $body['css'] = $css; + } + return $this->convert('/v1/convert/markdown', $body, $options); + } + + /** + * Convert a URL to PDF. + * + * @param string $url URL to convert + * @param PdfOptions|null $options PDF options + * @return string Raw PDF bytes + * @throws DocFastException + */ + public function url(string $url, ?PdfOptions $options = null): string + { + return $this->convert('/v1/convert/url', ['url' => $url], $options); + } + + /** + * List available templates. + * + * @return array + * @throws DocFastException + */ + public function templates(): array + { + $data = $this->get('/v1/templates'); + $result = json_decode($data, true); + return $result['templates'] ?? []; + } + + /** + * Render a template to PDF. + * + * @param string $templateId Template ID + * @param array $data Template data + * @return string Raw PDF bytes + * @throws DocFastException + */ + public function renderTemplate(string $templateId, array $data = []): string + { + return $this->post('/v1/templates/' . urlencode($templateId), ['data' => $data]); + } + + private function convert(string $path, array $body, ?PdfOptions $options): string + { + if ($options !== null) { + $body = array_merge($body, $options->toArray()); + } + return $this->post($path, $body); + } + + private function post(string $path, array $body): string + { + $ch = curl_init($this->baseUrl . $path); + curl_setopt_array($ch, [ + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => json_encode($body), + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => $this->timeout, + CURLOPT_HTTPHEADER => [ + 'Authorization: Bearer ' . $this->apiKey, + 'Content-Type: application/json', + 'Accept: application/pdf', + ], + ]); + + $response = curl_exec($ch); + $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $error = curl_error($ch); + curl_close($ch); + + if ($response === false) { + throw new DocFastException('Request failed: ' . $error, 0); + } + + if ($statusCode >= 400) { + $this->handleError($response, $statusCode); + } + + return $response; + } + + private function get(string $path): string + { + $ch = curl_init($this->baseUrl . $path); + curl_setopt_array($ch, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => $this->timeout, + CURLOPT_HTTPHEADER => [ + 'Authorization: Bearer ' . $this->apiKey, + 'Accept: application/json', + ], + ]); + + $response = curl_exec($ch); + $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $error = curl_error($ch); + curl_close($ch); + + if ($response === false) { + throw new DocFastException('Request failed: ' . $error, 0); + } + + if ($statusCode >= 400) { + $this->handleError($response, $statusCode); + } + + return $response; + } + + private function handleError(string $response, int $statusCode): never + { + $message = "HTTP $statusCode"; + $code = null; + + $data = json_decode($response, true); + if (is_array($data)) { + $message = $data['error'] ?? $message; + $code = $data['code'] ?? null; + } + + throw new DocFastException($message, $statusCode, $code); + } +} diff --git a/sdk/php/src/DocFastException.php b/sdk/php/src/DocFastException.php new file mode 100644 index 0000000..2b583f6 --- /dev/null +++ b/sdk/php/src/DocFastException.php @@ -0,0 +1,18 @@ +statusCode = $statusCode; + $this->errorCode = $errorCode; + parent::__construct($message, $statusCode, $previous); + } +} diff --git a/sdk/php/src/PdfOptions.php b/sdk/php/src/PdfOptions.php new file mode 100644 index 0000000..c22384c --- /dev/null +++ b/sdk/php/src/PdfOptions.php @@ -0,0 +1,65 @@ +$key !== null) { + $data[$key] = $this->$key; + } + } + return $data; + } +}