Chapter 10. Integration Guide & Code Samples

wxprint Last Updated: 2026-10-25 10:51 Views: 33

Chapter 10. Integration Guide & Code Samples

10.1 Integration patterns at a glance

Need How
A print button on a web page Include /sdk/wxprint.js → wp.print({template, data, printer})
Web preview-then-print wp.preview({...}) → tray window (or /preview/{job_id})
Backend document output POST /api/print (template id + inline data or a data URL) + polling
Data fetched at print time data: {url, headers} — the server fetches
Dynamic layout assembly POST /api/script verb sequences (or the SDK’s wp.document())
Batch shipping Pass an array as data + copies_per_record
LAN print server bind: "0.0.0.0" + token; every machine submits with the token
Archiving POST /api/saveas (pdf/png/zip) or export from the preview window

The repository’s examples/ directory contains runnable versions of every sample (this chapter’s code comes from there):

examples/
├── websocket/            Browser demos (1 pure HTML / 2 overlay / 3 Twig / 4 feature gallery)
└── http/                 Backend samples (python/php/nodejs/java/golang/
                          rust/c/cpp/csharp/ruby) + mock_api.py (URL data-source mock)

10.2 Quick verification with curl

# Is the service running?
curl -s http://127.0.0.1:8899/api/version

# List printers
curl -s http://127.0.0.1:8899/api/printers

# Submit a print (template id + inline data)
curl -s -X POST http://127.0.0.1:8899/api/print \
  -H "Content-Type: application/json" \
  -d '{"template": "invoice-a4", "data": {"no": "INV-001"},
       "printer": "HP-LaserJet-M1005"}'

# Check status
curl -s http://127.0.0.1:8899/api/jobs/<job_id>

10.3 Python (stdlib urllib)

import json, time, urllib.request

BASE, TOKEN = "http://127.0.0.1:8899", ""   # fill in when the service sets a token

def api(method, path, body=None, raw=False):
    req = urllib.request.Request(BASE + path, method=method)
    if TOKEN:
        req.add_header("Authorization", f"Bearer {TOKEN}")
    data = None
    if body is not None:
        data = json.dumps(body).encode()
        req.add_header("Content-Type", "application/json")
    with urllib.request.urlopen(req, data=data, timeout=30) as resp:
        payload = resp.read()
        return payload if raw else json.loads(payload)

# 1) List printers
printers = api("GET", "/api/printers")["printers"]
printer = next((p["name"] for p in printers if p["state"] == "ready"), "")

# 2) Submit: inline overlay template + inline data
template = {
    "id": "demo-waybill-py", "name": "Python sample waybill", "kind": "overlay",
    "page": {"width_mm": 100, "height_mm": 180},
    "elements": [
        {"type": "text", "id": "title", "x": 5, "y": 6, "w": 90, "h": 8,
         "text": "Shipping label (submitted from Python)",
         "font": {"family": "", "size_pt": 12, "weight": 700}},
        {"type": "barcode", "id": "bc", "x": 5, "y": 20, "w": 90, "h": 14,
         "symbology": "code128", "content": "{{no}}", "show_text": True},
        {"type": "text", "id": "addr", "x": 5, "y": 40, "w": 90, "h": 16,
         "text": "To: {{name}}\n{{addr}}", "font": {"family": "", "size_pt": 8}},
    ],
}
job = api("POST", "/api/print", {
    "template": template,
    "data": {"no": "PY-2026-0001", "name": "Alice",
             "addr": "100 Wensan Road, Xihu District, Hangzhou"},
    "printer": printer,
})
job_id = job["job_id"]

# 3) URL data form (fetched server-side; headers pass auth through)
job_url = api("POST", "/api/print", {
    "template": {"url": "https://erp.example.com/tpl/order-label.json"},
    "data": {"url": "https://erp.example.com/api/data/orders",   # array = batch
             "headers": {"Authorization": "Bearer erp-token"}, "timeout_s": 5},
})

# 4) Poll status
for _ in range(60):
    st = api("GET", f"/api/jobs/{job_id}")
    if st["status"] in ("done", "failed", "cancelled"):
        break
    time.sleep(0.5)

# 5) Register the template (inline submissions are not registered)
reg = api("POST", "/api/templates", template)

# 6) Preview PDF (no paper; reference by id)
pdf = api("POST", "/api/preview?format=pdf",
          {"template": "demo-waybill-py", "data": {"no": "PY-2026-0002"}},
          raw=True)
open("/tmp/wxprint-preview.pdf", "wb").write(pdf)

10.4 Node.js (built-in fetch, 18+)

const BASE = "http://127.0.0.1:8899";
const TOKEN = "";

async function api(method, path, body) {
  const headers = { "Content-Type": "application/json" };
  if (TOKEN) headers.Authorization = `Bearer ${TOKEN}`;
  const resp = await fetch(BASE + path, {
    method, headers,
    body: body === undefined ? undefined : JSON.stringify(body),
  });
  if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${await resp.text()}`);
  return resp.json();
}

// Batch printing: an array = 3 labels fed continuously
const job = await api("POST", "/api/print", {
  template: {
    id: "demo-label-node", kind: "overlay",
    page: { width_mm: 60, height_mm: 40 },
    elements: [
      { type: "text", id: "sku", x: 3, y: 3, w: 54, h: 6,
        text: "{{sku}}", font: { family: "", size_pt: 9, weight: 700 } },
      { type: "barcode", id: "bc", x: 3, y: 11, w: 54, h: 12,
        symbology: "code128", content: "{{code}}", show_text: false },
    ],
  },
  data: [
    { sku: "Wireless mouse (black)", code: "SKU-10001", qty: 24, bin: "A-01-03" },
    { sku: "Wireless mouse (white)", code: "SKU-10002", qty: 18, bin: "A-01-04" },
    { sku: "Wired keyboard 87-key", code: "SKU-10003", qty: 12, bin: "B-02-01" },
  ],
  printer: "",
});

for (let i = 0; i < 60; i++) {
  const st = await api("GET", `/api/jobs/${job.job_id}`);
  if (["done", "failed", "cancelled"].includes(st.status)) break;
  await new Promise(r => setTimeout(r, 500));
}

10.5 Java (java.net.http.HttpClient, JDK 11+)

import java.net.URI;
import java.net.http.*;
import java.time.Duration;

public class PrintWaybill {
    static final String BASE = "http://127.0.0.1:8899";
    static final String TOKEN = "";
    static final HttpClient HTTP = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(5)).build();

    static String api(String method, String path, String json) throws Exception {
        HttpRequest.Builder req = HttpRequest.newBuilder(URI.create(BASE + path))
                .timeout(Duration.ofSeconds(30))
                .header("Content-Type", "application/json");
        if (!TOKEN.isEmpty()) req.header("Authorization", "Bearer " + TOKEN);
        req.method(json == null ? "GET" : method,
                   json == null ? HttpRequest.BodyPublishers.noBody()
                               : HttpRequest.BodyPublishers.ofString(json));
        HttpResponse<String> resp = HTTP.send(req.build(),
                HttpResponse.BodyHandlers.ofString());
        if (resp.statusCode() >= 400)
            throw new RuntimeException("HTTP " + resp.statusCode() + ": " + resp.body());
        return resp.body();
    }

    public static void main(String[] args) throws Exception {
        // Submit: inline overlay template (prefer Jackson/Gson POJOs in production)
        String body = """
            { "template": {
                "id": "demo-waybill-java", "kind": "overlay",
                "page": { "width_mm": 100, "height_mm": 100 },
                "elements": [
                  { "type": "text", "id": "title", "x": 5, "y": 5, "w": 90, "h": 8,
                    "text": "Express waybill (submitted from Java)",
                    "font": { "family": "", "size_pt": 12, "weight": 700 } },
                  { "type": "barcode", "id": "bc", "x": 5, "y": 18, "w": 90, "h": 14,
                    "symbology": "code128", "content": "{{no}}", "show_text": true },
                  { "type": "qrcode", "id": "qr", "x": 62, "y": 38, "w": 32, "h": 32,
                    "content": "{{url}}" } ] },
              "data": { "no": "JAVA-2026-0001", "url": "https://example.com/t/1" },
              "printer": "" }
            """;
        System.out.println(api("POST", "/api/print", body));
    }
}

10.6 Go (stdlib net/http)

func api(method, path string, body any) (string, error) {
    var rd io.Reader
    if body != nil {
        b, _ := json.Marshal(body)
        rd = bytes.NewReader(b)
    }
    req, _ := http.NewRequest(method, "http://127.0.0.1:8899"+path, rd)
    req.Header.Set("Content-Type", "application/json")
    resp, err := http.DefaultClient.Do(req)
    if err != nil { return "", err }
    defer resp.Body.Close()
    b, _ := io.ReadAll(resp.Body)
    if resp.StatusCode >= 400 {
        return "", fmt.Errorf("HTTP %d: %s", resp.StatusCode, b)
    }
    return string(b), nil
}

// Inline html+twig template (entry = a Twig string rendered server-side)
job, err := api("POST", "/api/print", map[string]any{
    "template": map[string]any{
        "id": "demo-report-go", "kind": "html",
        "entry": `<h2>Inventory daily report ({{date}})</h2>
            <table border="1" cellpadding="4" style="border-collapse:collapse;width:100%">
            <tr style="background:#eee"><th>Warehouse</th><th>SKUs</th></tr>
            {% for w in warehouses %}<tr><td>{{w.name}}</td><td>{{w.sku_count}}</td></tr>{% endfor %}
            </table>`,
        "page": map[string]any{"width_mm": 210, "height_mm": 297,
            "margin_mm": map[string]float64{"top": 15, "bottom": 15, "left": 14, "right": 14}},
    },
    "data": map[string]any{"date": "2026-10-24",
        "warehouses": []map[string]any{{"name": "Hangzhou WH1", "sku_count": 1024}}},
})

10.7 PHP (cURL extension)

function api(string $method, string $path, ?array $body = null) {
    $ch = curl_init("http://127.0.0.1:8899" . $path);
    curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true,
                           CURLOPT_CUSTOMREQUEST  => $method,
                           CURLOPT_TIMEOUT        => 30]);
    $headers = ['Content-Type: application/json'];
    if (TOKEN !== '') { $headers[] = 'Authorization: Bearer ' . TOKEN; }
    if ($body !== null) {
        curl_setopt($ch, CURLOPT_POSTFIELDS,
                    json_encode($body, JSON_UNESCAPED_UNICODE));
    }
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    $resp = curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    if ($code >= 400) { fwrite(STDERR, "HTTP $code: $resp\n"); exit(1); }
    return json_decode($resp, true);
}

$job = api('POST', '/api/print', [
    'template' => 'invoice-a4',
    'data'     => ['no' => 'INV-2026-1042', 'amount' => 1280.50],
    'printer'  => 'HP-LaserJet-M1005',
]);

10.8 C# (.NET 8, zero NuGet dependencies)

using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;

using var http = new HttpClient { BaseAddress = new Uri("http://127.0.0.1:8899") };

var body = new {
    template = new {
        id = "demo-label-csharp", kind = "overlay",
        page = new { width_mm = 60, height_mm = 40 },
        elements = new object[] {
            new { type = "text", id = "title", x = 3, y = 3, w = 54, h = 6,
                  text = "{{title}}", font = new { family = "", size_pt = 8, weight = 700 } },
            new { type = "barcode", id = "bc", x = 3, y = 11, w = 54, h = 12,
                  symbology = "code128", content = "{{isbn}}", show_text = false },
        },
    },
    data = new { title = "Programming Rust (2nd ed.)", isbn = "978-7-111-00000-0" },
};
var resp = await http.PostAsJsonAsync("/api/print", body);
resp.EnsureSuccessStatusCode();
var job = await resp.Content.ReadFromJsonAsync<JsonElement>();
Console.WriteLine($"job_id={job.GetProperty("job_id")}");

10.9 Ruby (stdlib net/http)

require 'net/http'
require 'json'

def api(method, path, body = nil)
  uri = URI("http://127.0.0.1:8899#{path}")
  req = Net::HTTP.const_get(method.capitalize).new(uri)
  req['Content-Type'] = 'application/json'
  req.body = JSON.generate(body) if body
  Net::HTTP.start(uri.host, uri.port) { |http| http.request(req).body }
end

# 58mm receipt: inline html template (entry = a Twig string)
job = JSON.parse(api('post', '/api/print', {
  'template' => {
    'id' => 'demo-receipt-ruby', 'kind' => 'html',
    'entry' => <<~HTML,
      <div style="font-family:monospace">
        <h3 style="margin:0">{{shop}}</h3>
        {% for r in rows %}<div>{{r.name|escape}} × {{r.qty}} … {{r.price}}</div>{% endfor %}
        <div style="text-align:right"><b>Total: {{total}}</b></div>
      </div>
    HTML
    'page' => { 'width_mm' => 58, 'height_mm' => 120,
                'margin_mm' => { 'top' => 4, 'bottom' => 4, 'left' => 4, 'right' => 4 } }
  },
  'data' => { 'shop' => "Sample convenience store",
              'rows' => [{ 'name' => 'Coffee', 'qty' => 2, 'price' => 12 }],
              'total' => 24 },
}))

10.10 C / C++ / Rust

Samples for all three live in examples/http/{c,cpp,rust}/ (libcurl / pure std); the core is the same task: POST JSON to /api/print, parse the job_id, poll /api/jobs/{id}. Rust sketch (std only):

// Submit → parse job_id (JSON assembly/parsing omitted; see examples/http/rust)
let resp = http_post("http://127.0.0.1:8899/api/print", &body_json)?;
// Poll
loop {
    let st = http_get(&format!("http://127.0.0.1:8899/api/jobs/{job_id}"))?;
    if st.contains("\"done\"") || st.contains("\"failed\"") { break; }
    std::thread::sleep(std::time::Duration::from_millis(500));
}

10.11 Web integration

Option 1: the JS SDK (recommended)

<script src="http://127.0.0.1:8899/sdk/wxprint.js"></script>
<script>
  const wp = new wxprint.WxPrint({ token: "T" });
  wp.onJob(ev => updateProgress(ev.page_done, ev.total_pages));

  document.getElementById("print-btn").onclick = async () => {
    // Preview form: the tray pops the native preview window on done;
    // the user confirms and it goes to paper
    await wp.preview({ template: "invoice-a4",
                       data: { url: "/api/invoice/2026-1042" } });
    // Direct form: submitted and printed immediately (high-volume
    // waybill/receipt scenarios)
    // await wp.print({ template: "sf-waybill-100x93", data: {...},
    //                  printer: "HP-LaserJet-M1005" });
  };
</script>

Option 2: raw WebSocket (see the example in Section 5.4; you implement reconnection yourself — the SDK has it built in).

Popup-blocker note: if the preview confirmation page uses window.open, it must be called synchronously inside the user gesture (the click) — opening inside an async callback gets blocked. Open about:blank synchronously with a “generating…” placeholder, then set win.location.href to /preview/{job_id} when done.

10.12 The adaptive connection convention

The samples and demo pages follow one convention; business integrations are encouraged to adopt it:

  1. Environment variables WXPRINT_BASE / WXPRINT_TOKEN (explicit);
  2. ~/.wxprint/config.json (adapts automatically to the bind/token the tray configured; 0.0.0.0 resolves to 127.0.0.1);
  3. Default http://127.0.0.1:8899 anonymous.

The browser demos additionally accept URL parameters ?host=&token= as an explicit override.

10.13 Migrating from Lodop

No automatic migration tool; a complete verb-mapping matrix ships in docs/lodop-parity.md:

Lodop WxPrint
PRINT_INIT / PRINT_INITA init + pagesize
ADD_PRINT_TEXT/HTM/TABLE/BARCODE/QRCODE/IMAGE/RECT/LINE add_text / add_html / add_table / add_barcode / add_qrcode / add_image / add_rect / add_line
SET_PRINT_PAGESIZE (0.1mm units) pagesize (mm units; coordinates convert directly)
SET_PRINT_STYLE(A) set_style / set_stylea
NEWPAGE newpage
ItemType 1/2/3/4 (header/footer/first/last) element scope
PageNumber {{page}} / {{pages}}
PRINT / PRINTA / PRINT_PREVIEW / SAVEAS print / printer selection in the preview / preview / saveas (the element-level tweaks of PRINT_SETUP are a planned item)
ADD_PRINT_SETUP_BKIMG + SET_SHOW_MODE "BKIMG_PRINT" template background.image + background.print

Coordinates convert directly from 0.1mm to mm; field tweaks made in PRINT_SETUP use a private format and need redoing in the new system (template-level calibration_offset covers the common cases).

← Previous Article Chapter 11. Troubleshooting Next Article → Chapter 9. Tray & Print Preview

Comments

No comments yet