SAP Forms by Adobe - PDF-Formulare in ABAP Cloud

kategorie
Integration
Veröffentlicht
autor
Johannes

SAP Forms by Adobe ist der Cloud-native Dienst fuer professionelle PDF-Formulare auf SAP BTP. Er ersetzt die klassischen Adobe Forms (Transaktion SFP) und ermoeglicht die Generierung hochwertiger PDF-Dokumente mit dynamischen Inhalten, Tabellen und Barcodes.

Ueberblick und Unterschiede zu klassischen Adobe Forms

In On-Premise SAP-Systemen nutzt du Adobe Forms ueber die Transaktion SFP mit dem Adobe LiveCycle Designer. In ABAP Cloud ist dieser Ansatz nicht verfuegbar. Stattdessen bietet SAP den SAP Forms Service by Adobe als BTP-Service an.

Vergleich: Klassisch vs. Cloud

AspektKlassische Adobe Forms (SFP)SAP Forms Service by Adobe
ZugriffTransaktion SFP, SE78REST API auf BTP
Template-DesignAdobe LiveCycle Designer (lokal)Adobe LiveCycle Designer (lokal)
Template-SpeicherungSAP-System (Form Builder)BTP Document Repository
RenderingAdobe Document Services (ADS)Cloud-basierte Adobe Engine
LizenzSAP-StandardlizenzSeparate BTP-Lizenz erforderlich
IntegrationABAP-FunktionsbausteineHTTP/REST mit Communication Arrangement

Architektur

┌─────────────────────────────────────────────────────────────────────────────┐
│ SAP Forms by Adobe - Architektur │
│ │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ ABAP Cloud Environment │ │
│ │ │ │
│ │ ┌─────────────────────┐ ┌─────────────────────┐ │ │
│ │ │ Business Logic │────▶│ Forms Client │ │ │
│ │ │ (RAP/Klassen) │ │ (HTTP Client) │ │ │
│ │ └─────────────────────┘ └──────────┬──────────┘ │ │
│ │ │ │ │
│ └──────────────────────────────────────────┼───────────────────────────────┘ │
│ │ │
│ │ HTTPS/REST │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ SAP Forms Service by Adobe │ │
│ │ │ │
│ │ ┌─────────────────────┐ ┌─────────────────────┐ │ │
│ │ │ Template Storage │ │ Adobe Rendering │ │ │
│ │ │ (XDP Templates) │────▶│ Engine │ │ │
│ │ └─────────────────────┘ └──────────┬──────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────┐ │ │
│ │ │ PDF Output │ │ │
│ │ └─────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘

Service-Setup auf SAP BTP

Schritt 1: Service aktivieren

  1. Oeffne das SAP BTP Cockpit
  2. Navigiere zu Service Marketplace
  3. Suche nach SAP Forms Service by Adobe
  4. Erstelle eine Service Instance
  5. Erstelle einen Service Key fuer die Credentials

Schritt 2: Service Key analysieren

Der Service Key enthaelt die notwendigen Credentials:

{
"uri": "https://forms-service.cfapps.eu10.hana.ondemand.com",
"uaa": {
"clientid": "sb-forms-service!t12345",
"clientsecret": "abc123...",
"url": "https://your-tenant.authentication.eu10.hana.ondemand.com"
}
}

Schritt 3: Communication Arrangement einrichten

Erstelle in ABAP Cloud ein Communication Arrangement fuer den Forms Service.

Communication Scenario (ADT):

<?xml version="1.0" encoding="utf-8"?>
<scn:scenario xmlns:scn="http://sap.com/communication-scenario"
id="Z_FORMS_ADOBE_SERVICE"
displayName="SAP Forms by Adobe Service">
<scn:communicationType>HTTP</scn:communicationType>
<scn:outboundServices>
<scn:outboundService id="FORMS_RENDER_API">
<scn:technicalName>FORMS_RENDER_API</scn:technicalName>
<scn:description>PDF Rendering API</scn:description>
</scn:outboundService>
<scn:outboundService id="FORMS_TEMPLATE_API">
<scn:technicalName>FORMS_TEMPLATE_API</scn:technicalName>
<scn:description>Template Management API</scn:description>
</scn:outboundService>
</scn:outboundServices>
<scn:supportedAuthenticationMethods>
<scn:supportedAuthenticationMethod>OAUTH2_CLIENT_CREDENTIALS</scn:supportedAuthenticationMethod>
</scn:supportedAuthenticationMethods>
</scn:scenario>

Fiori App “Maintain Communication Arrangements”:

  1. Communication System erstellen mit Forms Service URL
  2. OAuth 2.0 Client Credentials aus Service Key eintragen
  3. Communication Arrangement mit deinem Scenario aktivieren

Template Definition und Upload

Templates werden mit Adobe LiveCycle Designer erstellt und als XDP-Dateien gespeichert.

XDP-Template Struktur

Ein XDP-Template besteht aus:

  • Masterpage: Seitenlayout, Kopf-/Fusszeilen
  • Body Pages: Dynamischer Inhalt
  • Data Schema: XML-Struktur fuer Daten
  • Scripts: JavaScript fuer Logik

Template erstellen

  1. Oeffne Adobe LiveCycle Designer
  2. Erstelle ein neues Formular
  3. Definiere die Data Connection mit XML Schema
  4. Gestalte das Layout mit Feldern, Tabellen, Bildern
  5. Speichere als .xdp Datei

Template-Upload per API

" Template Upload zum Forms Service
CLASS zcl_forms_template_manager DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
METHODS:
upload_template
IMPORTING iv_template_name TYPE string
iv_template_content TYPE xstring
RETURNING VALUE(rv_success) TYPE abap_bool
RAISING cx_http_dest_provider_error
cx_web_http_client_error.
METHODS:
list_templates
RETURNING VALUE(rt_templates) TYPE string_table
RAISING cx_http_dest_provider_error
cx_web_http_client_error.
METHODS:
delete_template
IMPORTING iv_template_name TYPE string
RETURNING VALUE(rv_success) TYPE abap_bool
RAISING cx_http_dest_provider_error
cx_web_http_client_error.
PRIVATE SECTION.
CONSTANTS:
gc_comm_scenario TYPE string VALUE 'Z_FORMS_ADOBE_SERVICE',
gc_service_id TYPE string VALUE 'FORMS_TEMPLATE_API'.
METHODS:
get_http_client
RETURNING VALUE(ro_client) TYPE REF TO if_web_http_client
RAISING cx_http_dest_provider_error
cx_web_http_client_error.
ENDCLASS.
CLASS zcl_forms_template_manager IMPLEMENTATION.
METHOD upload_template.
DATA(lo_client) = get_http_client( ).
TRY.
DATA(lo_request) = lo_client->get_http_request( ).
" Multipart Request fuer Template Upload
lo_request->set_header_field(
i_name = 'Content-Type'
i_value = 'application/octet-stream'
).
" Template-Name als Query Parameter
lo_request->set_uri_path(
|/v1/templates/{ iv_template_name }|
).
" Template-Content als Body
lo_request->set_binary( iv_template_content ).
" PUT Request fuer Upload
DATA(lo_response) = lo_client->execute( if_web_http_client=>put ).
DATA(lv_status) = lo_response->get_status( ).
rv_success = xsdbool( lv_status-code = 200 OR lv_status-code = 201 ).
CLEANUP.
lo_client->close( ).
ENDTRY.
ENDMETHOD.
METHOD list_templates.
DATA(lo_client) = get_http_client( ).
TRY.
DATA(lo_request) = lo_client->get_http_request( ).
lo_request->set_uri_path( '/v1/templates' ).
DATA(lo_response) = lo_client->execute( if_web_http_client=>get ).
IF lo_response->get_status( )-code = 200.
" JSON Response parsen
DATA(lv_json) = lo_response->get_text( ).
" Templates aus JSON extrahieren
" ...
ENDIF.
CLEANUP.
lo_client->close( ).
ENDTRY.
ENDMETHOD.
METHOD delete_template.
DATA(lo_client) = get_http_client( ).
TRY.
DATA(lo_request) = lo_client->get_http_request( ).
lo_request->set_uri_path( |/v1/templates/{ iv_template_name }| ).
DATA(lo_response) = lo_client->execute( if_web_http_client=>delete ).
rv_success = xsdbool( lo_response->get_status( )-code = 204 ).
CLEANUP.
lo_client->close( ).
ENDTRY.
ENDMETHOD.
METHOD get_http_client.
DATA(lo_destination) = cl_http_destination_provider=>create_by_comm_arrangement(
comm_scenario = gc_comm_scenario
service_id = gc_service_id
).
ro_client = cl_web_http_client_manager=>create_by_http_destination( lo_destination ).
ENDMETHOD.
ENDCLASS.

PDF-Generierung mit Daten

Die Kernfunktion des Forms Service ist die PDF-Generierung aus Template und Daten.

Rendering API aufrufen

" SAP Forms Service - PDF Rendering Client
CLASS zcl_forms_pdf_renderer DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_render_options,
output_type TYPE string, " PDF, PCL, PS, ZPL
tagged_pdf TYPE abap_bool,
embed_fonts TYPE abap_bool,
locale TYPE string,
END OF ty_render_options.
TYPES:
BEGIN OF ty_render_result,
pdf_content TYPE xstring,
page_count TYPE i,
file_size TYPE i,
render_time TYPE i, " Millisekunden
success TYPE abap_bool,
error_message TYPE string,
END OF ty_render_result.
METHODS:
render_pdf
IMPORTING iv_template_name TYPE string
iv_xml_data TYPE xstring
is_options TYPE ty_render_options OPTIONAL
RETURNING VALUE(rs_result) TYPE ty_render_result
RAISING cx_http_dest_provider_error
cx_web_http_client_error.
METHODS:
render_pdf_with_json
IMPORTING iv_template_name TYPE string
iv_json_data TYPE string
is_options TYPE ty_render_options OPTIONAL
RETURNING VALUE(rs_result) TYPE ty_render_result
RAISING cx_http_dest_provider_error
cx_web_http_client_error.
PRIVATE SECTION.
CONSTANTS:
gc_comm_scenario TYPE string VALUE 'Z_FORMS_ADOBE_SERVICE',
gc_service_id TYPE string VALUE 'FORMS_RENDER_API'.
METHODS:
build_request_json
IMPORTING iv_template_name TYPE string
iv_data TYPE string
iv_data_type TYPE string " xml oder json
is_options TYPE ty_render_options
RETURNING VALUE(rv_json) TYPE string.
ENDCLASS.
CLASS zcl_forms_pdf_renderer IMPLEMENTATION.
METHOD render_pdf.
" XML-Daten zu Base64 konvertieren
DATA(lv_xml_base64) = cl_web_http_utility=>encode_base64( iv_xml_data ).
" Request JSON aufbauen
DATA(lv_request) = build_request_json(
iv_template_name = iv_template_name
iv_data = lv_xml_base64
iv_data_type = 'xml'
is_options = is_options
).
" HTTP Client erstellen
DATA(lo_destination) = cl_http_destination_provider=>create_by_comm_arrangement(
comm_scenario = gc_comm_scenario
service_id = gc_service_id
).
DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination( lo_destination ).
TRY.
DATA(lo_request) = lo_client->get_http_request( ).
lo_request->set_header_field(
i_name = 'Content-Type'
i_value = 'application/json'
).
lo_request->set_header_field(
i_name = 'Accept'
i_value = 'application/json'
).
lo_request->set_uri_path( '/v1/documents/render' ).
lo_request->set_text( lv_request ).
" Zeitmessung starten
DATA(lv_start) = utclong_current( ).
" Request ausfuehren
DATA(lo_response) = lo_client->execute( if_web_http_client=>post ).
" Zeitmessung beenden
DATA(lv_end) = utclong_current( ).
rs_result-render_time = CONV i( utclong_diff( high = lv_end low = lv_start ) * 1000 ).
" Response verarbeiten
DATA(lv_status) = lo_response->get_status( ).
IF lv_status-code = 200.
" Erfolg - PDF extrahieren
DATA(lv_response_json) = lo_response->get_text( ).
" JSON parsen und PDF Base64 extrahieren
" Vereinfachte Extraktion:
DATA lv_pdf_base64 TYPE string.
" ... JSON Parsing ...
rs_result-pdf_content = cl_web_http_utility=>decode_base64( lv_pdf_base64 ).
rs_result-file_size = xstrlen( rs_result-pdf_content ).
rs_result-success = abap_true.
ELSE.
rs_result-success = abap_false.
rs_result-error_message = lo_response->get_text( ).
ENDIF.
CLEANUP.
lo_client->close( ).
ENDTRY.
ENDMETHOD.
METHOD render_pdf_with_json.
" JSON-Daten direkt verwenden
DATA(lv_request) = build_request_json(
iv_template_name = iv_template_name
iv_data = iv_json_data
iv_data_type = 'json'
is_options = is_options
).
" ... (analog zu render_pdf)
ENDMETHOD.
METHOD build_request_json.
" Request Body fuer Forms Service API
DATA(lv_output) = COND string(
WHEN is_options-output_type IS NOT INITIAL
THEN is_options-output_type
ELSE 'PDF'
).
rv_json = |\{| &&
|"templateSource": "storageName",| &&
|"templateName": "{ iv_template_name }",| &&
|"outputType": "{ lv_output }",| &&
|"taggedPdf": { COND string( WHEN is_options-tagged_pdf = abap_true THEN 'true' ELSE 'false' ) },| &&
|"embedFonts": { COND string( WHEN is_options-embed_fonts = abap_true THEN 'true' ELSE 'false' ) },|.
IF is_options-locale IS NOT INITIAL.
rv_json = rv_json && |"locale": "{ is_options-locale }",|.
ENDIF.
IF iv_data_type = 'xml'.
rv_json = rv_json && |"xmlData": "{ iv_data }"|.
ELSE.
rv_json = rv_json && |"data": { iv_data }|.
ENDIF.
rv_json = rv_json && |\}|.
ENDMETHOD.
ENDCLASS.

Rechnungsdaten aufbereiten

" XML-Daten Generator fuer Forms Service
CLASS zcl_invoice_form_data DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_item,
position TYPE i,
material_id TYPE string,
description TYPE string,
quantity TYPE p LENGTH 10 DECIMALS 3,
unit TYPE string,
unit_price TYPE p LENGTH 15 DECIMALS 2,
total TYPE p LENGTH 15 DECIMALS 2,
END OF ty_item,
ty_items TYPE STANDARD TABLE OF ty_item WITH KEY position.
TYPES:
BEGIN OF ty_invoice,
invoice_number TYPE string,
invoice_date TYPE d,
due_date TYPE d,
company_name TYPE string,
company_address TYPE string,
company_city TYPE string,
company_country TYPE string,
company_vat_id TYPE string,
customer_number TYPE string,
customer_name TYPE string,
customer_address TYPE string,
customer_city TYPE string,
customer_country TYPE string,
items TYPE ty_items,
subtotal TYPE p LENGTH 15 DECIMALS 2,
tax_rate TYPE p LENGTH 5 DECIMALS 2,
tax_amount TYPE p LENGTH 15 DECIMALS 2,
total TYPE p LENGTH 15 DECIMALS 2,
currency TYPE string,
payment_terms TYPE string,
bank_name TYPE string,
bank_iban TYPE string,
bank_bic TYPE string,
END OF ty_invoice.
METHODS:
generate_xml
IMPORTING is_invoice TYPE ty_invoice
RETURNING VALUE(rv_xml) TYPE xstring.
METHODS:
generate_json
IMPORTING is_invoice TYPE ty_invoice
RETURNING VALUE(rv_json) TYPE string.
ENDCLASS.
CLASS zcl_invoice_form_data IMPLEMENTATION.
METHOD generate_xml.
" XML-Dokument aufbauen
DATA(lo_ixml) = cl_ixml=>create( ).
DATA(lo_doc) = lo_ixml->create_document( ).
" Root Element
DATA(lo_root) = lo_doc->create_element( name = 'Invoice' ).
lo_doc->append_child( lo_root ).
" Namespace fuer Adobe Forms
lo_root->set_attribute(
name = 'xmlns:xfa'
value = 'http://www.xfa.org/schema/xfa-data/1.0/'
).
" === Header Sektion ===
DATA(lo_header) = lo_doc->create_element( name = 'Header' ).
lo_root->append_child( lo_header ).
DATA(lo_inv_no) = lo_doc->create_element( name = 'InvoiceNumber' ).
lo_inv_no->set_value( is_invoice-invoice_number ).
lo_header->append_child( lo_inv_no ).
DATA(lo_inv_date) = lo_doc->create_element( name = 'InvoiceDate' ).
lo_inv_date->set_value( |{ is_invoice-invoice_date DATE = USER }| ).
lo_header->append_child( lo_inv_date ).
DATA(lo_due_date) = lo_doc->create_element( name = 'DueDate' ).
lo_due_date->set_value( |{ is_invoice-due_date DATE = USER }| ).
lo_header->append_child( lo_due_date ).
" === Company (Absender) ===
DATA(lo_company) = lo_doc->create_element( name = 'Company' ).
lo_root->append_child( lo_company ).
DATA(lo_comp_name) = lo_doc->create_element( name = 'Name' ).
lo_comp_name->set_value( is_invoice-company_name ).
lo_company->append_child( lo_comp_name ).
DATA(lo_comp_addr) = lo_doc->create_element( name = 'Address' ).
lo_comp_addr->set_value( is_invoice-company_address ).
lo_company->append_child( lo_comp_addr ).
DATA(lo_comp_city) = lo_doc->create_element( name = 'City' ).
lo_comp_city->set_value( is_invoice-company_city ).
lo_company->append_child( lo_comp_city ).
DATA(lo_comp_country) = lo_doc->create_element( name = 'Country' ).
lo_comp_country->set_value( is_invoice-company_country ).
lo_company->append_child( lo_comp_country ).
DATA(lo_vat_id) = lo_doc->create_element( name = 'VatId' ).
lo_vat_id->set_value( is_invoice-company_vat_id ).
lo_company->append_child( lo_vat_id ).
" === Customer (Empfaenger) ===
DATA(lo_customer) = lo_doc->create_element( name = 'Customer' ).
lo_root->append_child( lo_customer ).
DATA(lo_cust_no) = lo_doc->create_element( name = 'CustomerNumber' ).
lo_cust_no->set_value( is_invoice-customer_number ).
lo_customer->append_child( lo_cust_no ).
DATA(lo_cust_name) = lo_doc->create_element( name = 'Name' ).
lo_cust_name->set_value( is_invoice-customer_name ).
lo_customer->append_child( lo_cust_name ).
DATA(lo_cust_addr) = lo_doc->create_element( name = 'Address' ).
lo_cust_addr->set_value( is_invoice-customer_address ).
lo_customer->append_child( lo_cust_addr ).
DATA(lo_cust_city) = lo_doc->create_element( name = 'City' ).
lo_cust_city->set_value( is_invoice-customer_city ).
lo_customer->append_child( lo_cust_city ).
DATA(lo_cust_country) = lo_doc->create_element( name = 'Country' ).
lo_cust_country->set_value( is_invoice-customer_country ).
lo_customer->append_child( lo_cust_country ).
" === Items (Positionen) ===
DATA(lo_items) = lo_doc->create_element( name = 'Items' ).
lo_root->append_child( lo_items ).
LOOP AT is_invoice-items INTO DATA(ls_item).
DATA(lo_item) = lo_doc->create_element( name = 'Item' ).
lo_items->append_child( lo_item ).
DATA(lo_pos) = lo_doc->create_element( name = 'Position' ).
lo_pos->set_value( |{ ls_item-position }| ).
lo_item->append_child( lo_pos ).
DATA(lo_mat) = lo_doc->create_element( name = 'MaterialId' ).
lo_mat->set_value( ls_item-material_id ).
lo_item->append_child( lo_mat ).
DATA(lo_desc) = lo_doc->create_element( name = 'Description' ).
lo_desc->set_value( ls_item-description ).
lo_item->append_child( lo_desc ).
DATA(lo_qty) = lo_doc->create_element( name = 'Quantity' ).
lo_qty->set_value( |{ ls_item-quantity }| ).
lo_item->append_child( lo_qty ).
DATA(lo_unit) = lo_doc->create_element( name = 'Unit' ).
lo_unit->set_value( ls_item-unit ).
lo_item->append_child( lo_unit ).
DATA(lo_price) = lo_doc->create_element( name = 'UnitPrice' ).
lo_price->set_value( |{ ls_item-unit_price }| ).
lo_item->append_child( lo_price ).
DATA(lo_total) = lo_doc->create_element( name = 'Total' ).
lo_total->set_value( |{ ls_item-total }| ).
lo_item->append_child( lo_total ).
ENDLOOP.
" === Totals ===
DATA(lo_totals) = lo_doc->create_element( name = 'Totals' ).
lo_root->append_child( lo_totals ).
DATA(lo_subtotal) = lo_doc->create_element( name = 'Subtotal' ).
lo_subtotal->set_value( |{ is_invoice-subtotal }| ).
lo_totals->append_child( lo_subtotal ).
DATA(lo_tax_rate) = lo_doc->create_element( name = 'TaxRate' ).
lo_tax_rate->set_value( |{ is_invoice-tax_rate }| ).
lo_totals->append_child( lo_tax_rate ).
DATA(lo_tax_amt) = lo_doc->create_element( name = 'TaxAmount' ).
lo_tax_amt->set_value( |{ is_invoice-tax_amount }| ).
lo_totals->append_child( lo_tax_amt ).
DATA(lo_grand) = lo_doc->create_element( name = 'GrandTotal' ).
lo_grand->set_value( |{ is_invoice-total }| ).
lo_totals->append_child( lo_grand ).
DATA(lo_curr) = lo_doc->create_element( name = 'Currency' ).
lo_curr->set_value( is_invoice-currency ).
lo_totals->append_child( lo_curr ).
" === Payment Info ===
DATA(lo_payment) = lo_doc->create_element( name = 'Payment' ).
lo_root->append_child( lo_payment ).
DATA(lo_terms) = lo_doc->create_element( name = 'Terms' ).
lo_terms->set_value( is_invoice-payment_terms ).
lo_payment->append_child( lo_terms ).
DATA(lo_bank) = lo_doc->create_element( name = 'BankName' ).
lo_bank->set_value( is_invoice-bank_name ).
lo_payment->append_child( lo_bank ).
DATA(lo_iban) = lo_doc->create_element( name = 'IBAN' ).
lo_iban->set_value( is_invoice-bank_iban ).
lo_payment->append_child( lo_iban ).
DATA(lo_bic) = lo_doc->create_element( name = 'BIC' ).
lo_bic->set_value( is_invoice-bank_bic ).
lo_payment->append_child( lo_bic ).
" XML zu xstring
DATA(lo_stream) = lo_ixml->create_stream_factory( )->create_ostream_xstring( rv_xml ).
DATA(lo_renderer) = lo_ixml->create_renderer(
document = lo_doc
ostream = lo_stream
).
lo_renderer->render( ).
ENDMETHOD.
METHOD generate_json.
" JSON-Serialisierung fuer Forms Service
rv_json = /ui2/cl_json=>serialize(
data = is_invoice
compress = abap_true
pretty_name = /ui2/cl_json=>pretty_mode-camel_case
).
ENDMETHOD.
ENDCLASS.

Dynamische Tabellen in Formularen

Dynamische Tabellen sind ein Kernelement professioneller Formulare. Sie wachsen automatisch mit den Daten.

Template-Design fuer Tabellen

Im Adobe LiveCycle Designer:

  1. Fuege eine Table aus der Palette ein
  2. Setze die Tabelle auf Dynamic (Eigenschaften > Pagination)
  3. Binde die Header Row an statische Werte
  4. Binde die Body Row an den wiederholenden Datenpfad (z.B. $.Invoice.Items.Item[*])

XML-Struktur fuer Tabellen

<Invoice>
<Items>
<Item>
<Position>1</Position>
<Description>Produkt A</Description>
<Quantity>10</Quantity>
<UnitPrice>99.00</UnitPrice>
<Total>990.00</Total>
</Item>
<Item>
<Position>2</Position>
<Description>Produkt B</Description>
<Quantity>5</Quantity>
<UnitPrice>149.00</UnitPrice>
<Total>745.00</Total>
</Item>
<!-- Weitere Items... -->
</Items>
</Invoice>

Seitenumbruch in Tabellen

" Tabellen mit automatischem Seitenumbruch
" Im Template: Table > Pagination = "Continue filling from previous page"
" ABAP: Daten aufbereiten - das Template handhabt den Umbruch
METHOD generate_multi_page_invoice.
" Alle Items in einer Liste - Forms Service teilt automatisch auf Seiten
LOOP AT lt_order_items INTO DATA(ls_item).
APPEND VALUE ty_item(
position = ls_item-posnr
description = ls_item-arktx
quantity = ls_item-kwmeng
unit_price = ls_item-netpr
total = ls_item-netwr
) TO ls_invoice-items.
ENDLOOP.
" XML generieren
DATA(lv_xml) = NEW zcl_invoice_form_data( )->generate_xml( ls_invoice ).
" PDF rendern - automatischer Seitenumbruch bei langen Tabellen
DATA(lo_renderer) = NEW zcl_forms_pdf_renderer( ).
rs_result = lo_renderer->render_pdf(
iv_template_name = 'INVOICE_TEMPLATE'
iv_xml_data = lv_xml
is_options = VALUE #( output_type = 'PDF' )
).
ENDMETHOD.

Gruppierung und Zwischensummen

" Positionen mit Gruppierung nach Kategorie
TYPES:
BEGIN OF ty_category_group,
category_name TYPE string,
items TYPE ty_items,
subtotal TYPE p LENGTH 15 DECIMALS 2,
END OF ty_category_group,
ty_category_groups TYPE STANDARD TABLE OF ty_category_group WITH KEY category_name.
METHOD generate_grouped_xml.
" XML mit Gruppen fuer mehrstufige Tabellen
DATA(lo_doc) = cl_ixml=>create( )->create_document( ).
DATA(lo_root) = lo_doc->create_element( name = 'Invoice' ).
lo_doc->append_child( lo_root ).
" Kategorien
DATA(lo_categories) = lo_doc->create_element( name = 'Categories' ).
lo_root->append_child( lo_categories ).
LOOP AT it_groups INTO DATA(ls_group).
DATA(lo_category) = lo_doc->create_element( name = 'Category' ).
lo_categories->append_child( lo_category ).
DATA(lo_cat_name) = lo_doc->create_element( name = 'Name' ).
lo_cat_name->set_value( ls_group-category_name ).
lo_category->append_child( lo_cat_name ).
DATA(lo_items) = lo_doc->create_element( name = 'Items' ).
lo_category->append_child( lo_items ).
" Items der Kategorie
LOOP AT ls_group-items INTO DATA(ls_item).
DATA(lo_item) = lo_doc->create_element( name = 'Item' ).
lo_items->append_child( lo_item ).
" ... Item-Felder ...
ENDLOOP.
" Zwischensumme
DATA(lo_subtotal) = lo_doc->create_element( name = 'Subtotal' ).
lo_subtotal->set_value( |{ ls_group-subtotal }| ).
lo_category->append_child( lo_subtotal ).
ENDLOOP.
" XML zu xstring
" ...
ENDMETHOD.

Barcode-Integration in Formulare

Barcodes und QR-Codes sind wichtig fuer automatisierte Verarbeitung und Tracking.

Unterstuetzte Barcode-Typen

TypBeschreibungAnwendung
Code 128Alphanumerisch, variabelAllgemein, Logistik
EAN-1313 ZiffernProduktkennzeichnung
QR-Code2D, hohe KapazitaetURLs, Zahlungsdaten
Data Matrix2D, kompaktIndustrielle Anwendungen
PDF4172D, hohe DichteAusweise, Transportdokumente

Barcode im Template

Im Adobe LiveCycle Designer:

  1. Fuege ein Barcode Objekt aus der Palette ein
  2. Waehle den Barcode-Typ (z.B. QR Code)
  3. Binde das Data-Feld an den XML-Pfad
  4. Konfiguriere Groesse und Fehlerkorrektur

XML mit Barcode-Daten

" Barcode-Daten in XML aufnehmen
METHOD add_barcode_data.
DATA(lo_barcodes) = lo_doc->create_element( name = 'Barcodes' ).
lo_root->append_child( lo_barcodes ).
" QR-Code mit Zahlungsinformationen (GiroCode/EPC QR)
DATA(lo_qr) = lo_doc->create_element( name = 'PaymentQR' ).
lo_qr->set_value( build_girocode(
iv_iban = ls_invoice-bank_iban
iv_bic = ls_invoice-bank_bic
iv_recipient = ls_invoice-company_name
iv_amount = ls_invoice-total
iv_reference = ls_invoice-invoice_number
) ).
lo_barcodes->append_child( lo_qr ).
" Code128 fuer Rechnungsnummer
DATA(lo_barcode) = lo_doc->create_element( name = 'InvoiceBarcode' ).
lo_barcode->set_value( ls_invoice-invoice_number ).
lo_barcodes->append_child( lo_barcode ).
ENDMETHOD.
" GiroCode (EPC QR Code) generieren
METHOD build_girocode.
" EPC QR Code Format
" Service Tag: BCD
" Version: 002
" Character Set: 1 (UTF-8)
" Identification: SCT (SEPA Credit Transfer)
rv_girocode = |BCD\n| &&
|002\n| &&
|1\n| &&
|SCT\n| &&
|{ iv_bic }\n| &&
|{ iv_recipient }\n| &&
|{ iv_iban }\n| &&
|EUR{ iv_amount }\n| &&
|\n| && " Purpose (leer)
|{ iv_reference }\n| && " Reference
|\n|. " Text (leer)
ENDMETHOD.

Dynamische Barcode-Generierung

" Barcode-Generator fuer verschiedene Typen
CLASS zcl_barcode_generator DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_barcode_data,
type TYPE string, " QR, CODE128, EAN13, DATAMATRIX
content TYPE string,
width TYPE i,
height TYPE i,
END OF ty_barcode_data.
METHODS:
" Tracking-Barcode fuer Pakete
create_tracking_barcode
IMPORTING iv_tracking_id TYPE string
RETURNING VALUE(rs_barcode) TYPE ty_barcode_data.
METHODS:
" QR-Code fuer Produktinfo-URL
create_product_qr
IMPORTING iv_product_id TYPE string
RETURNING VALUE(rs_barcode) TYPE ty_barcode_data.
METHODS:
" EPC/GiroCode fuer Zahlungen
create_payment_qr
IMPORTING iv_iban TYPE string
iv_bic TYPE string
iv_recipient TYPE string
iv_amount TYPE p
iv_currency TYPE string
iv_reference TYPE string
RETURNING VALUE(rs_barcode) TYPE ty_barcode_data.
ENDCLASS.
CLASS zcl_barcode_generator IMPLEMENTATION.
METHOD create_tracking_barcode.
rs_barcode-type = 'CODE128'.
rs_barcode-content = iv_tracking_id.
rs_barcode-width = 200.
rs_barcode-height = 50.
ENDMETHOD.
METHOD create_product_qr.
rs_barcode-type = 'QR'.
" URL zur Produktseite
rs_barcode-content = |https://products.example.com/{ iv_product_id }|.
rs_barcode-width = 100.
rs_barcode-height = 100.
ENDMETHOD.
METHOD create_payment_qr.
rs_barcode-type = 'QR'.
" EPC/GiroCode Format
rs_barcode-content =
|BCD\n| &&
|002\n| &&
|1\n| &&
|SCT\n| &&
|{ iv_bic }\n| &&
|{ iv_recipient }\n| &&
|{ iv_iban }\n| &&
|{ iv_currency }{ iv_amount }\n| &&
|\n| &&
|{ iv_reference }|.
rs_barcode-width = 150.
rs_barcode-height = 150.
ENDMETHOD.
ENDCLASS.

RAP Integration

Die Integration in RAP ermoeglicht PDF-Generierung direkt aus Business Objects.

Behavior Definition

managed implementation in class ZBP_I_SALESORDER unique;
strict ( 2 );
define behavior for ZI_SalesOrder alias SalesOrder
persistent table zsalesorder
lock master
authorization master ( instance )
{
create;
update;
delete;
// PDF-Generierung als Action
action generateInvoicePdf result [1] $self;
// Download Action mit Binary Result
static action downloadInvoice
parameter ZA_InvoiceDownloadParam
result [1] ZA_PdfDownloadResult;
field ( readonly ) OrderUUID, CreatedAt, CreatedBy;
field ( readonly : update ) OrderNumber;
}

Action Implementation

" RAP Behavior Implementation fuer PDF-Generierung
CLASS lhc_salesorder DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS generateInvoicePdf FOR MODIFY
IMPORTING keys FOR ACTION SalesOrder~generateInvoicePdf RESULT result.
METHODS downloadInvoice FOR MODIFY
IMPORTING keys FOR ACTION SalesOrder~downloadInvoice RESULT result.
METHODS:
prepare_invoice_data
IMPORTING iv_order_uuid TYPE sysuuid_x16
RETURNING VALUE(rs_invoice) TYPE zcl_invoice_form_data=>ty_invoice.
ENDCLASS.
CLASS lhc_salesorder IMPLEMENTATION.
METHOD generateInvoicePdf.
" Auftragsdaten lesen
READ ENTITIES OF zi_salesorder IN LOCAL MODE
ENTITY SalesOrder
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).
LOOP AT lt_orders INTO DATA(ls_order).
" Rechnungsdaten aufbereiten
DATA(ls_invoice) = prepare_invoice_data( ls_order-OrderUUID ).
TRY.
" XML generieren
DATA(lo_data_gen) = NEW zcl_invoice_form_data( ).
DATA(lv_xml) = lo_data_gen->generate_xml( ls_invoice ).
" PDF rendern
DATA(lo_renderer) = NEW zcl_forms_pdf_renderer( ).
DATA(ls_pdf_result) = lo_renderer->render_pdf(
iv_template_name = 'Z_INVOICE_TEMPLATE'
iv_xml_data = lv_xml
is_options = VALUE #(
output_type = 'PDF'
embed_fonts = abap_true
)
).
IF ls_pdf_result-success = abap_true.
" PDF als Attachment speichern (optional)
" Oder: Status aktualisieren
APPEND VALUE #(
%tky = ls_order-%tky
%param = ls_order
) TO result.
" Erfolgsmeldung
APPEND VALUE #(
%tky = ls_order-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-success
text = |Rechnung { ls_invoice-invoice_number } wurde generiert|
)
) TO reported-salesorder.
ELSE.
" Fehler vom Forms Service
APPEND VALUE #(
%tky = ls_order-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = ls_pdf_result-error_message
)
) TO reported-salesorder.
ENDIF.
CATCH cx_http_dest_provider_error
cx_web_http_client_error INTO DATA(lx_http).
APPEND VALUE #(
%tky = ls_order-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |Verbindungsfehler: { lx_http->get_text( ) }|
)
) TO reported-salesorder.
ENDTRY.
ENDLOOP.
ENDMETHOD.
METHOD downloadInvoice.
" Parameter auslesen
DATA(ls_param) = keys[ 1 ]-%param.
" Auftrag laden
SELECT SINGLE *
FROM zsalesorder
WHERE order_number = @ls_param-OrderNumber
INTO @DATA(ls_order).
IF sy-subrc <> 0.
APPEND VALUE #(
%tky = keys[ 1 ]-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |Auftrag { ls_param-OrderNumber } nicht gefunden|
)
) TO reported-salesorder.
RETURN.
ENDIF.
" Rechnungsdaten und PDF generieren
DATA(ls_invoice) = prepare_invoice_data( ls_order-order_uuid ).
TRY.
DATA(lo_data_gen) = NEW zcl_invoice_form_data( ).
DATA(lv_xml) = lo_data_gen->generate_xml( ls_invoice ).
DATA(lo_renderer) = NEW zcl_forms_pdf_renderer( ).
DATA(ls_pdf_result) = lo_renderer->render_pdf(
iv_template_name = 'Z_INVOICE_TEMPLATE'
iv_xml_data = lv_xml
).
IF ls_pdf_result-success = abap_true.
" Ergebnis zurueckgeben
APPEND VALUE #(
%tky = keys[ 1 ]-%tky
%param = VALUE #(
FileName = |Rechnung_{ ls_param-OrderNumber }.pdf|
MimeType = 'application/pdf'
FileContent = ls_pdf_result-pdf_content
FileSize = ls_pdf_result-file_size
)
) TO result.
ENDIF.
CATCH cx_root INTO DATA(lx_error).
APPEND VALUE #(
%tky = keys[ 1 ]-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = lx_error->get_text( )
)
) TO reported-salesorder.
ENDTRY.
ENDMETHOD.
METHOD prepare_invoice_data.
" Auftragskopf laden
SELECT SINGLE *
FROM zsalesorder
WHERE order_uuid = @iv_order_uuid
INTO @DATA(ls_order).
" Kundendaten laden
SELECT SINGLE *
FROM zcustomer
WHERE customer_id = @ls_order-customer_id
INTO @DATA(ls_customer).
" Positionen laden
SELECT *
FROM zsalesorder_item
WHERE order_uuid = @iv_order_uuid
ORDER BY position
INTO TABLE @DATA(lt_items).
" Daten zusammenstellen
rs_invoice-invoice_number = ls_order-order_number.
rs_invoice-invoice_date = sy-datum.
rs_invoice-due_date = sy-datum + 30.
rs_invoice-customer_number = ls_customer-customer_id.
rs_invoice-customer_name = ls_customer-name.
rs_invoice-customer_address = ls_customer-street.
rs_invoice-customer_city = |{ ls_customer-postal_code } { ls_customer-city }|.
rs_invoice-customer_country = ls_customer-country.
" Company-Daten (aus Customizing)
rs_invoice-company_name = 'Meine Firma GmbH'.
rs_invoice-company_address = 'Hauptstrasse 1'.
rs_invoice-company_city = '12345 Berlin'.
rs_invoice-company_country = 'Deutschland'.
rs_invoice-company_vat_id = 'DE123456789'.
" Items konvertieren
LOOP AT lt_items INTO DATA(ls_item).
APPEND VALUE #(
position = ls_item-position
material_id = ls_item-material_id
description = ls_item-description
quantity = ls_item-quantity
unit = ls_item-unit
unit_price = ls_item-unit_price
total = ls_item-quantity * ls_item-unit_price
) TO rs_invoice-items.
rs_invoice-subtotal = rs_invoice-subtotal + ls_item-quantity * ls_item-unit_price.
ENDLOOP.
" Steuern berechnen
rs_invoice-tax_rate = '19.00'.
rs_invoice-tax_amount = rs_invoice-subtotal * rs_invoice-tax_rate / 100.
rs_invoice-total = rs_invoice-subtotal + rs_invoice-tax_amount.
rs_invoice-currency = 'EUR'.
" Zahlungsinformationen
rs_invoice-payment_terms = 'Zahlbar innerhalb von 30 Tagen'.
rs_invoice-bank_name = 'Sparkasse Berlin'.
rs_invoice-bank_iban = 'DE89370400440532013000'.
rs_invoice-bank_bic = 'COBADEFFXXX'.
ENDMETHOD.
ENDCLASS.

Lizenzierung und Kosten

SAP Forms Service by Adobe Lizenz

AspektDetails
LizenzmodellSubscription-basiert auf SAP BTP
PreismetrikAnzahl der API-Aufrufe pro Monat
Free TierTypischerweise 1.000 Renderings/Monat
Standard Tier~0,01-0,05 EUR pro Rendering (volumenabhaengig)
EnterpriseIndividuelle Vereinbarung

Kostenfaktoren

  1. Rendering-Volumen: Anzahl der PDF-Generierungen
  2. Dokumentgroesse: Anzahl Seiten beeinflusst Rendering-Zeit
  3. Template-Speicher: Speicherplatz fuer XDP-Templates
  4. Region: Preise variieren nach BTP-Region

Kostenoptimierung

" Caching von Templates und haeufigen PDFs
CLASS zcl_pdf_cache DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
METHODS:
get_cached_pdf
IMPORTING iv_cache_key TYPE string
EXPORTING ev_pdf TYPE xstring
ev_cache_hit TYPE abap_bool.
METHODS:
cache_pdf
IMPORTING iv_cache_key TYPE string
iv_pdf TYPE xstring
iv_ttl TYPE i DEFAULT 3600. " 1 Stunde
ENDCLASS.
" Verwendung: Standarddokumente cachen
METHOD generate_with_cache.
" Cache-Key basierend auf Dokumentinhalt
DATA(lv_hash) = calculate_hash( ls_data ).
DATA(lv_cache_key) = |INVOICE_{ lv_hash }|.
" Cache pruefen
DATA(lo_cache) = NEW zcl_pdf_cache( ).
lo_cache->get_cached_pdf(
EXPORTING iv_cache_key = lv_cache_key
IMPORTING ev_pdf = rv_pdf
ev_cache_hit = DATA(lv_hit)
).
IF lv_hit = abap_true.
RETURN. " Cached PDF zurueckgeben
ENDIF.
" Neu generieren
rv_pdf = generate_pdf( ls_data ).
" Im Cache speichern
lo_cache->cache_pdf(
iv_cache_key = lv_cache_key
iv_pdf = rv_pdf
iv_ttl = 86400 " 24 Stunden
).
ENDMETHOD.

Fehlerbehandlung

Robuste PDF-Generierung

" PDF-Service mit umfassender Fehlerbehandlung
CLASS zcl_robust_forms_service DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_generation_result,
success TYPE abap_bool,
pdf_content TYPE xstring,
error_code TYPE string,
error_message TYPE string,
retry_after TYPE i, " Sekunden bis Retry
END OF ty_generation_result.
METHODS:
generate_pdf_safe
IMPORTING is_invoice TYPE zcl_invoice_form_data=>ty_invoice
iv_template TYPE string
iv_max_retries TYPE i DEFAULT 3
RETURNING VALUE(rs_result) TYPE ty_generation_result.
PRIVATE SECTION.
CONSTANTS:
" Fehlertypen
gc_error_network TYPE string VALUE 'NETWORK',
gc_error_timeout TYPE string VALUE 'TIMEOUT',
gc_error_template TYPE string VALUE 'TEMPLATE',
gc_error_data TYPE string VALUE 'DATA',
gc_error_service TYPE string VALUE 'SERVICE',
gc_error_quota TYPE string VALUE 'QUOTA'.
METHODS:
classify_error
IMPORTING iv_http_status TYPE i
iv_error_message TYPE string
EXPORTING ev_error_code TYPE string
ev_is_retryable TYPE abap_bool.
METHODS:
wait_with_backoff
IMPORTING iv_attempt TYPE i.
ENDCLASS.
CLASS zcl_robust_forms_service IMPLEMENTATION.
METHOD generate_pdf_safe.
DATA: lv_attempt TYPE i VALUE 0,
lo_renderer TYPE REF TO zcl_forms_pdf_renderer.
" Data Generator
DATA(lo_data_gen) = NEW zcl_invoice_form_data( ).
TRY.
" XML-Daten generieren
DATA(lv_xml) = lo_data_gen->generate_xml( is_invoice ).
CATCH cx_root INTO DATA(lx_data).
rs_result-success = abap_false.
rs_result-error_code = gc_error_data.
rs_result-error_message = lx_data->get_text( ).
RETURN.
ENDTRY.
" Renderer erstellen
lo_renderer = NEW zcl_forms_pdf_renderer( ).
" Retry-Loop
WHILE lv_attempt < iv_max_retries.
lv_attempt = lv_attempt + 1.
TRY.
DATA(ls_render_result) = lo_renderer->render_pdf(
iv_template_name = iv_template
iv_xml_data = lv_xml
).
IF ls_render_result-success = abap_true.
" Erfolg
rs_result-success = abap_true.
rs_result-pdf_content = ls_render_result-pdf_content.
RETURN.
ELSE.
" Service-Fehler analysieren
classify_error(
EXPORTING
iv_http_status = 0
iv_error_message = ls_render_result-error_message
IMPORTING
ev_error_code = rs_result-error_code
ev_is_retryable = DATA(lv_retryable)
).
IF lv_retryable = abap_false.
" Nicht retry-faehiger Fehler
rs_result-success = abap_false.
rs_result-error_message = ls_render_result-error_message.
RETURN.
ENDIF.
ENDIF.
CATCH cx_http_dest_provider_error INTO DATA(lx_dest).
rs_result-error_code = gc_error_network.
rs_result-error_message = lx_dest->get_text( ).
CATCH cx_web_http_client_error INTO DATA(lx_http).
classify_error(
EXPORTING
iv_http_status = 0 " Nicht verfuegbar bei Exception
iv_error_message = lx_http->get_text( )
IMPORTING
ev_error_code = rs_result-error_code
ev_is_retryable = lv_retryable
).
IF lv_retryable = abap_false.
rs_result-success = abap_false.
rs_result-error_message = lx_http->get_text( ).
RETURN.
ENDIF.
ENDTRY.
" Warten vor naechstem Versuch
IF lv_attempt < iv_max_retries.
wait_with_backoff( lv_attempt ).
ENDIF.
ENDWHILE.
" Alle Versuche fehlgeschlagen
rs_result-success = abap_false.
rs_result-error_message = |PDF-Generierung nach { iv_max_retries } Versuchen fehlgeschlagen: | &&
rs_result-error_message.
ENDMETHOD.
METHOD classify_error.
" Fehlertyp aus HTTP-Status oder Message ableiten
CASE iv_http_status.
WHEN 429.
ev_error_code = gc_error_quota.
ev_is_retryable = abap_true.
WHEN 500 OR 502 OR 503 OR 504.
ev_error_code = gc_error_service.
ev_is_retryable = abap_true.
WHEN 400 OR 404.
ev_error_code = gc_error_template.
ev_is_retryable = abap_false.
WHEN OTHERS.
" Aus Message ableiten
IF iv_error_message CS 'timeout' OR iv_error_message CS 'timed out'.
ev_error_code = gc_error_timeout.
ev_is_retryable = abap_true.
ELSEIF iv_error_message CS 'template not found'.
ev_error_code = gc_error_template.
ev_is_retryable = abap_false.
ELSE.
ev_error_code = gc_error_network.
ev_is_retryable = abap_true.
ENDIF.
ENDCASE.
ENDMETHOD.
METHOD wait_with_backoff.
" Exponential Backoff: 1s, 2s, 4s...
DATA(lv_wait_seconds) = 2 ** ( iv_attempt - 1 ).
" In ABAP Cloud: Einfache Warteimplementierung
" (Alternative: cl_abap_timers oder Background Processing)
DATA(lv_end_time) = utclong_current( ) + CONV utclong( lv_wait_seconds ).
WHILE utclong_current( ) < lv_end_time.
" Warten
ENDWHILE.
ENDMETHOD.
ENDCLASS.

Best Practices

Template-Design

EmpfehlungBeschreibung
ModularWiederverwendbare Subforms fuer Header, Footer, Tabellen
FlexibelDynamische Tabellen statt fixer Zeilen
TestbarTest-Datenset fuer alle Edge Cases
WartbarKlare Namenskonventionen fuer Felder

Performance

  1. Template-Caching: Templates nicht bei jedem Aufruf hochladen
  2. Batch-Generierung: Mehrere PDFs in einem Request (wenn API unterstuetzt)
  3. Asynchron: Grosse PDFs im Hintergrund generieren
  4. Komprimierung: PDF-Komprimierung aktivieren

Sicherheit

  1. Sensible Daten: Keine Credentials in Templates
  2. Input-Validierung: XML/JSON-Daten vor Uebergabe validieren
  3. Zugriffskontrolle: RAP Authorization fuer PDF-Actions
  4. Audit: PDF-Generierungen protokollieren

Weiterfuehrende Themen

Zusammenfassung

SAP Forms by Adobe in ABAP Cloud bietet professionelle PDF-Generierung:

  1. Service-Setup: Communication Arrangement mit OAuth 2.0 Credentials
  2. Template-Design: XDP-Templates mit Adobe LiveCycle Designer erstellen
  3. PDF-Rendering: REST API mit XML/JSON-Daten aufrufen
  4. Dynamische Tabellen: Automatischer Seitenumbruch und Gruppierung
  5. Barcode-Integration: QR-Codes fuer Zahlungen, Code128 fuer Tracking
  6. RAP-Integration: Actions fuer PDF-Generierung und Download
  7. Lizenzierung: Subscription-basiert, Kosten nach Rendering-Volumen

Der Service ersetzt klassische Adobe Forms auf BTP vollstaendig und ermoeglicht hochwertige Geschaeftsdokumente in der Cloud.