ubertech-za/asciidoctor-wrapper

PHP wrapper for Asciidoctor with configurable styling and theming support

0.1.0 2025-09-16 20:24 UTC

This package is auto-updated.

Last update: 2025-09-16 20:46:56 UTC


README

⚠️ BETA SOFTWARE NOTICE This package is currently in beta and is being prepared for testing in upcoming projects. Please expect possible breaking changes in future releases. We do not recommend using this package in production environments without thorough testing.

A Laravel package that provides a PHP wrapper around AsciiDoctor and AsciiDoctor-PDF with advanced theming, styling, and configuration management.

Latest Version License Tests

Overview

This package bridges the gap between PHP applications and the powerful AsciiDoctor ecosystem, providing:

  • Configurable AsciiDoctor Integration: Support for both asciidoctor and asciidoctor-pdf executables
  • Advanced Theming System: Create, extend, and manage themes with JSON configuration
  • Style Object Composition: Programmatically build themes using fluent APIs
  • Theme Inheritance: Extend built-in AsciiDoctor themes or custom themes
  • Laravel Integration: Service providers, facades, and configuration management
  • Type Safety: Full PHP 8.2+ type declarations and comprehensive testing

Installation

Install the package via Composer:

composer require ubertech-za/asciidoctor-wrapper

Prerequisites

You need to have AsciiDoctor installed on your system:

# Install AsciiDoctor (Ruby required)
gem install asciidoctor

# Install AsciiDoctor-PDF for PDF generation
gem install asciidoctor-pdf

Alternatively, use Docker:

docker pull asciidoctor/docker-asciidoctor

Laravel Integration

Configuration

Publish the configuration file:

php artisan vendor:publish --tag=asciidoctor-config

Configure your executables in config/asciidoctor.php:

return [
    'executables' => [
        'asciidoctor' => env('ASCIIDOCTOR_PATH', 'asciidoctor'),
        'asciidoctor_pdf' => env('ASCIIDOCTOR_PDF_PATH', 'asciidoctor-pdf'),
    ],
    
    'default_theme' => env('ASCIIDOCTOR_DEFAULT_THEME', 'default'),
    'themes_path' => env('ASCIIDOCTOR_THEMES_PATH', storage_path('asciidoc/themes')),
    'templates_path' => env('ASCIIDOCTOR_TEMPLATES_PATH', storage_path('asciidoc/templates')),
    'fonts_path' => env('ASCIIDOCTOR_FONTS_PATH', storage_path('asciidoc/fonts')),
    'output_path' => env('ASCIIDOCTOR_OUTPUT_PATH', storage_path('asciidoc/documents')),
    
    // ... additional configuration
];

Environment Variables

Add to your .env file:

ASCIIDOCTOR_PATH=/usr/local/bin/asciidoctor
ASCIIDOCTOR_PDF_PATH=/usr/local/bin/asciidoctor-pdf
ASCIIDOCTOR_DEFAULT_THEME=default-with-font-fallbacks
ASCIIDOCTOR_THEMES_PATH=/path/to/themes
ASCIIDOCTOR_TEMPLATES_PATH=/path/to/templates
ASCIIDOCTOR_FONTS_PATH=/path/to/your/fonts
ASCIIDOCTOR_OUTPUT_PATH=/path/to/output
ASCIIDOCTOR_CACHE_ENABLED=true
ASCIIDOCTOR_CACHE_TTL=3600

Usage

Basic Usage

use UbertechZa\AsciidoctorWrapper\Facades\Asciidoctor;

// Convert AsciiDoc to HTML
$result = Asciidoctor::convert(
    input: 'document.adoc',
    output: 'document.html',
    format: 'html5'
);

// Convert to PDF
$result = Asciidoctor::convert(
    input: 'document.adoc',
    output: 'document.pdf',
    format: 'pdf'
);

if ($result['success']) {
    echo "Conversion successful!";
} else {
    echo "Error: " . $result['error'];
}

Using the Wrapper Class Directly

use UbertechZa\AsciidoctorWrapper\AsciidoctorWrapper;

$wrapper = new AsciidoctorWrapper(
    asciidoctorPath: '/usr/local/bin/asciidoctor',
    asciidoctorPdfPath: '/usr/local/bin/asciidoctor-pdf'
);

$result = $wrapper->convert('input.adoc', 'output.pdf', 'pdf');

Theme System

Creating Themes with the Theme Builder

use UbertechZa\AsciidoctorWrapper\Builder\ThemeBuilder;

$theme = app(ThemeBuilder::class)
    ->name('modern-theme')
    ->extending('default')
    ->withColors([
        'primary' => '#2563eb',
        'secondary' => '#64748b',
        'accent' => '#059669',
        'text' => '#0f172a',
        'background' => '#ffffff'
    ])
    ->withFont('heading', 'Inter', '16pt', 'bold')
    ->withFont('body', 'Inter', '11pt', 'normal')
    ->withFont('mono', 'JetBrains Mono', '9pt', 'normal')
    ->withElementStyle('document', [
        'color' => '#0f172a',
        'font' => ['family' => 'Inter', 'size' => '11pt']
    ])
    ->withElementStyle('heading1', [
        'color' => '#2563eb',
        'font' => ['family' => 'Inter', 'size' => '24pt', 'weight' => 'bold']
    ])
    ->build();

// Use the theme
$result = Asciidoctor::convert('input.adoc', 'output.pdf', 'pdf', $theme);

Loading Themes from JSON

Create a theme file at resources/asciidoctor/themes/my-theme.json:

{
  "name": "my-theme",
  "extends": "default",
  "colors": {
    "primary": "#2563eb",
    "secondary": "#64748b",
    "accent": "#059669",
    "text": "#0f172a",
    "background": "#ffffff"
  },
  "fonts": {
    "heading": {
      "family": "Inter",
      "size": "16pt",
      "weight": "bold"
    },
    "body": {
      "family": "Inter",
      "size": "11pt",
      "weight": "normal"
    },
    "mono": {
      "family": "JetBrains Mono",
      "size": "9pt",
      "weight": "normal"
    }
  },
  "styles": {
    "document": {
      "color": "#0f172a",
      "font": {
        "family": "Inter",
        "size": "11pt"
      }
    },
    "heading1": {
      "color": "#2563eb",
      "font": {
        "family": "Inter",
        "size": "24pt",
        "weight": "bold"
      }
    }
  }
}

Load and use the theme:

use UbertechZa\AsciidoctorWrapper\StyleManager;

$styleManager = app(StyleManager::class);
$theme = $styleManager->loadFromJson(resource_path('asciidoctor/themes/my-theme.json'));

$result = Asciidoctor::convert('input.adoc', 'output.pdf', 'pdf', $theme);

Theme Inheritance

Themes can extend other themes:

{
  "name": "dark-theme",
  "extends": "modern-theme",
  "colors": {
    "text": "#ffffff",
    "background": "#1a1a1a"
  }
}

Built-in themes can also be extended:

{
  "name": "custom-default",
  "extends": "default",
  "colors": {
    "primary": "#ff6b35"
  }
}

Font Management

Custom Fonts Configuration

The package supports custom fonts for PDF generation. Fonts are automatically loaded from the configured fonts directory and made available to asciidoctor-pdf themes.

Setting Up Custom Fonts

  1. Configure the fonts directory in config/asciidoctor.php:
'fonts_path' => env('ASCIIDOCTOR_FONTS_PATH', storage_path('asciidoc/fonts')),
  1. Add font files to your fonts directory:
storage/asciidoc/fonts/
├── MyFont-Regular.ttf
├── MyFont-Bold.ttf
├── MyFont-Italic.ttf
├── MyFont-BoldItalic.ttf
├── MonoFont-Regular.ttf
└── MonoFont-Bold.ttf
  1. Use fonts in themes - the package automatically generates the font catalog:
$theme = app(ThemeBuilder::class)
    ->name('custom-font-theme')
    ->extending('default-with-font-fallbacks')
    ->withColors([
        'primary' => '#2563eb',
        'text' => '#0f172a'
    ])
    ->build();

Font Catalog Generation

When using custom fonts, the package automatically:

  • Scans the fonts_path directory for TTF files
  • Generates a proper font catalog in the theme YAML
  • Sets the pdf-fontsdir attribute for asciidoctor-pdf
  • Maps fonts to appropriate theme elements (base, heading, code)

Example generated theme YAML:

extends: default-with-font-fallbacks

font:
  catalog:
    merge: true
    MyFont:
      normal: MyFont-Regular.ttf
      bold: MyFont-Bold.ttf
      italic: MyFont-Italic.ttf
      bold_italic: MyFont-BoldItalic.ttf
    MonoFont:
      normal: MonoFont-Regular.ttf
      bold: MonoFont-Bold.ttf

base:
  font_family: MyFont
  font_size: 11
  font_color: 0f172a

heading:
  font_family: MyFont
  font_color: 2563eb
  font_style: bold

code:
  font_family: MonoFont
  font_size: 9

Font Environment Variable

Set the fonts directory via environment variable:

# Use absolute path
ASCIIDOCTOR_FONTS_PATH=/usr/share/fonts/custom

# Or relative to Laravel storage
ASCIIDOCTOR_FONTS_PATH=/path/to/laravel/storage/asciidoc/fonts

Supported Font Formats

  • TTF (TrueType) - Primary supported format
  • OTF (OpenType) - Also supported by asciidoctor-pdf

Font Naming Convention

For automatic detection of font variants, use this naming pattern:

  • FontName-Regular.ttf (normal weight)
  • FontName-Bold.ttf (bold weight)
  • FontName-Italic.ttf (italic style)
  • FontName-BoldItalic.ttf (bold italic)

Supported Output Formats

  • html / html5 - HTML output
  • docbook / docbook5 - DocBook XML
  • manpage - Unix manual pages
  • pdf - PDF documents (requires asciidoctor-pdf)
  • docx - Microsoft Word documents
// Generate multiple formats
$formats = ['html5', 'pdf', 'docx'];

foreach ($formats as $format) {
    $result = Asciidoctor::convert(
        input: 'document.adoc',
        output: "document.{$format}",
        format: $format,
        theme: $theme
    );
}

Advanced Configuration

Custom Attributes

$result = Asciidoctor::convert(
    input: 'document.adoc',
    output: 'document.html',
    format: 'html5',
    attributes: [
        'source-highlighter' => 'rouge',
        'icons' => 'font',
        'sectanchors' => true,
        'toc' => 'left',
        'toclevels' => 3
    ]
);

Safe Mode

$result = Asciidoctor::convert(
    input: 'document.adoc',
    output: 'document.html',
    format: 'html5',
    safeMode: 'safe' // unsafe, safe, server, secure
);

Style Objects

Color Styles

use UbertechZa\AsciidoctorWrapper\Style\ColorStyle;

$colors = new ColorStyle(
    primary: '#2563eb',
    secondary: '#64748b',
    accent: '#059669',
    text: '#0f172a',
    background: '#ffffff'
);

// Validate colors
if ($colors->isValidHex('#2563eb')) {
    echo "Valid hex color!";
}

Font Styles

use UbertechZa\AsciidoctorWrapper\Style\FontStyle;

$font = new FontStyle(
    family: 'Inter',
    size: '12pt',
    weight: 'bold',
    style: 'normal'
);

// Convert to array for processing
$fontArray = $font->toArray();

Element Styles

use UbertechZa\AsciidoctorWrapper\Style\ElementStyle;

$heading = new ElementStyle(
    color: '#2563eb',
    backgroundColor: '#ffffff',
    font: new FontStyle('Inter', '18pt', 'bold'),
    margin: '1em 0',
    padding: '0.5em'
);

Testing and Validation

Check Executable Availability

$wrapper = app(AsciidoctorWrapper::class);

if ($wrapper->isAsciidoctorAvailable()) {
    echo "AsciiDoctor is available!";
}

if ($wrapper->isAsciidoctorPdfAvailable()) {
    echo "AsciiDoctor-PDF is available!";
}

$formats = $wrapper->getSupportedFormats();
// ['html', 'html5', 'docbook', 'docbook5', 'manpage', 'pdf', 'docx']

Theme Validation

use UbertechZa\AsciidoctorWrapper\StyleManager;

$styleManager = app(StyleManager::class);
$theme = $styleManager->loadFromJson('theme.json');

if ($styleManager->validateTheme($theme)) {
    echo "Theme is valid!";
} else {
    $errors = $styleManager->getValidationErrors();
    foreach ($errors as $error) {
        echo "Error: {$error}\n";
    }
}

Artisan Commands

The package doesn't include Artisan commands by default, but you can create your own:

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use UbertechZa\AsciidoctorWrapper\Facades\Asciidoctor;
use UbertechZa\AsciidoctorWrapper\Builder\ThemeBuilder;

class ConvertToAsciidoc extends Command
{
    protected $signature = 'asciidoc:convert {input} {output} {--format=html5} {--theme=}';
    protected $description = 'Convert AsciiDoc files to various formats';

    public function handle()
    {
        $input = $this->argument('input');
        $output = $this->argument('output');
        $format = $this->option('format');
        $themeName = $this->option('theme');
        
        $theme = null;
        if ($themeName) {
            $theme = app(ThemeBuilder::class)->name($themeName)->build();
        }
        
        $result = Asciidoctor::convert($input, $output, $format, $theme);
        
        if ($result['success']) {
            $this->info("✅ Successfully converted {$input} to {$output}");
        } else {
            $this->error("❌ Conversion failed: " . $result['error']);
        }
    }
}

Error Handling

The wrapper returns structured results:

$result = Asciidoctor::convert('input.adoc', 'output.pdf', 'pdf');

if ($result['success']) {
    echo "Success! Output: " . $result['output'];
} else {
    echo "Error: " . $result['error'];
    echo "Exit Code: " . $result['exit_code'];
    if (!empty($result['stderr'])) {
        echo "STDERR: " . $result['stderr'];
    }
}

Performance and Caching

Theme Caching

Enable theme caching in your configuration:

'cache' => [
    'enabled' => env('ASCIIDOCTOR_CACHE_ENABLED', true),
    'path' => storage_path('app/asciidoctor/cache'),
    'ttl' => env('ASCIIDOCTOR_CACHE_TTL', 3600),
],

Best Practices

  1. Reuse Theme Objects: Create themes once and reuse them for multiple conversions
  2. Validate Early: Use validateTheme() during development to catch issues
  3. Cache Compiled Themes: Enable caching for production environments
  4. Monitor Resources: AsciiDoctor can be memory-intensive for large documents

Contributing

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/amazing-feature
  3. Run tests: composer test
  4. Commit changes: git commit -am 'Add amazing feature'
  5. Push to branch: git push origin feature/amazing-feature
  6. Create a Pull Request

Development Setup

git clone https://github.com/ubertech-za/asciidoctor-wrapper.git
cd asciidoctor-wrapper
composer install
composer test

Testing

Run the test suite:

composer test

# Run with coverage
composer test-coverage

# Run specific test
vendor/bin/pest tests/Unit/StyleManagerTest.php

# Run integration tests
vendor/bin/pest tests/Feature/

License

This package is open-sourced software licensed under the MIT license.

Changelog

Please see CHANGELOG for more information on what has changed recently.

Credits

This package is inspired by the excellent work of:

Support

Made by Uber Technologies cc