Tech brief for developers who ship report-related products for Clarion: preview replacements, export/document generators, report add-on templates, or anything that reads report page files or implements the report generator interface. What changed, when it appears, and the transition path for each integration style.
Companion to the Clarion Unicode (USTRING) — Tester Guide. Items marked known limitation are deliberate in this build — not bugs to report.
A report can opt into Unicode with the UNICODE attribute
(REPORT,...,UNICODE). Opted-in preview page files are
Windows Enhanced Metafiles (.emf) instead of classic
placeable WMF. That is the whole delta third-party tools see.
Without the attribute, pages stay byte-identical WMF — same as every prior Clarion
release. Since build 14313 the attribute is no longer the only switch, and for this beta
release EMF is the default for generated applications (a default we want your
feedback on) — plan for install bases that see
.emf on every report, not just the ones a developer marked by hand. Three
levers turn a report wide, all documented in the tester guide's
What's new (Reports and the
report previewer):
Report{PROP:Unicode} = True as its report opens; a per-procedure
“Unicode pages” prompt opts one report out (for the one report that feeds a
WMF-only tool).Report{PROP:Unicode} = TRUE after OPEN and
before the first PRINT, for a hand-coded report; latched by the first PRINT; read it to
learn whether a report prints EMF, by attribute or by property.report_unicode=>on (project
Defines, or PRAGMA('define(report_unicode=>on)') at the top of a module)
gives every REPORT structure the UNICODE attribute as it compiles — the template check
writes it into the project as well (§8a).
The same build replaces the shipped previewer: ReportPreviewClass is the
default Print Previewer, and an application whose prompt still reads
PrintPreviewClass generates the new class on its next generate (a derived or
third-party class named there is left alone). Its text index,
PageTextIndexClass, is GUI-free and yours to reuse — §8a.
Why EMF: WMF text records are byte-only; they cannot carry UTF-16. EMF
uses EXTTEXTOUTW and is the native Windows metafile format. Page model is
unchanged: one file per page, same preview queue, same properties — only the on-disk
format differs.
Do not route by filename. Read the header:
9AC6CDD7h.ENHMETAHEADER —
iType = 1 (EMR_HEADER) and dSignature =
464D4520h (" EMF").Both checks are a few dozen bytes. One header fork covers both formats with no config and no knowledge of whether the report opted in.
.emf. It now reads the header. Prefer content detection over the filename.
| Product | Transition | Section |
|---|---|---|
A. Uses or derives from shipped WMFParser /
WMFDocumentParser (ABWMFPAR) |
Rebuild. | §4 |
B. Implements IReportGenerator (export target) |
No break. Wide surface shipped, opt-in (IReportGeneratorW) |
§5 |
| C. Own page-file parser | Real work; this note + shipped source are the map | §6 |
| D. Displays/prints pages (preview, viewer) | Easier than WMF — Windows plays EMF natively | §8 |
Paths compose (often B on A, or D with a slice of C for band navigation). Read every section that applies.
This build's shipped WMFParser already reads both formats.
The §2 header check is built in. .emf pages use the same delivery surface
(ProcessString, ProcessText, ProcessBand,
ProcessImage, fonts, shapes, band/control markers). Coordinates stay in
1/1000-inch space.
Transition: recompile against this build's LibSrc. ABWMFPAR is LibSrc source (not a DLL). Rebuild is the upgrade.
Checks:
.emf page — old ProcessHeader
raises a fatal-class "not a valid WMF file" error; the app
terminates via the runtime halt box and may leave partial output.
Rebuild removes that floor. Customers hit it when an opted-in report meets a tool
still on the old parser.After rebuild, text fidelity matches today: wide page text is narrowed to ANSI (best-fit, same as the WMF lane). Wide delivery to generators is the next step — see §5.
Generators work unchanged in this build. The shipped parser feeds
.emf pages through the same narrow IReportGenerator methods
(ProcessString(…, STRING Text, …), ProcessText with
CSTRING(257) lines, rest of ABRPTGEN.INT). Wide page text is narrowed before
it reaches the interface — best-fit codepage, same as WMF. Recompile and you are done for
this build.
IReportGeneratorW (Option A2, refined)
The decision landed as A2 — zero break — refined one step further:
IReportGeneratorW is a standalone companion interface
(it does not inherit IReportGenerator), so adopting means implementing
exactly seven methods next to your existing ones:
OpenDocumentW(UNSIGNED TotalPages),BYTE — called instead
of OpenDocument on a wide-armed run, before any output is
written. Decide your output encoding here; the signal is registration-driven and
page-kind-independent.ProcessStringW / ProcessCheckW / ProcessRadioW / ProcessGroupW
(…, USTRING Text, …) and
ProcessTextW(TextFormatWQueue, …) — the text payloads, UTF-16 units
preserved (TextFormatWQueue rows carry USTRING(257)
lines).ProcessImageW(*ImageFormatGrp pFormatGrp, STRING iName, STRING pExtendControlAttr)
— an alpha page image: the engine's color-emoji stamp (§6h) as a
32-bpp BGRA DIB at pFormatGrp.EmbedStart in the page file (premultiplied
when AF=1); pExtendControlAttr carries
SCA=<SourceConstantAlpha>;AF=<AlphaFormat>;BITS=<file offset of the bits>.
Delivered only to a wide-armed target, and after the owning control's text
has been flushed — paint it on top. A target with no picture lane implements it as a
no-op. Added after 08-26: an existing IReportGeneratorW
implementation must add this method to compile against the refreshed build.
Everything else — pages, shapes, ordinary images, properties — keeps riding the
narrow interface (only the emoji stamps arrive through ProcessImageW). Clarion has no runtime interface query, so registration is an
explicit handoff: the narrow ref goes to the parser via
Init as always; the wide ref via
WMFDocumentParser.SetWideGenerator after Init, or the
two-ref selector overload
AddItem(gen.IReportGenerator, gen.IReportGeneratorW). The shipped
report-output templates do this automatically — a wide-capable target registers both
faces exactly when the procedure's REPORT carries UNICODE, and every
other combination generates the classic narrow registration byte-identical.
A generator that never adopts keeps compiling and receiving best-fit narrowed text,
indefinitely. The shipped TEXT, HTML and XML targets have adopted (UTF-8 output,
honest charset/encoding declarations), and so has the PDF target (Type0/CIDFontType2
fonts with glyph subsets and ToUnicode; emoji stamps through ProcessImageW
as RGB + soft mask) — see section 7. A
narrow-only target fed wide content that actually loses characters posts one
“Report Export Notice” per document.
Feedback still useful: would your product adopt the wide surface, and on which targets first?
The real transition work. Shortcut: this build's LibSrc ABWMFPAR.CLW is a complete working EMF page parser in Clarion — header fork, record walk, comment channel, transforms, images. That file's EMF arms are the reference for everything below.
Pages come from the Clarion print engine only — a closed dialect, not "all of EMF":
EMR_EXTTEXTOUTW (84) — payload
UTF-16LE (§6f). Strings containing emoji record in a partitioned
form — see §6h.EMR_ALPHABLEND (114)
(§6h).EMR_EXTCREATEFONTINDIRECTW (82) —
LOGFONTW, wide face names, lfCharSet as usual.EMR_STRETCHDIBITS (§6e).EMR_GDICOMMENT (70) (§6d).EMR_SELECTOBJECT /
EMR_DELETEOBJECT with EMF's explicit handle table (§6c).Mechanical traps vs WMF:
nSize is in BYTES; WMF sizes are in WORDs. A
size * 2 advance walks into garbage.iType (4) + nSize (4); advance by
nSize.
WMF pages stored final device coordinates in WORD fields (engine already flattened). EMF
records under SETWORLDTRANSFORM /
MODIFYWORLDTRANSFORM stacked with SAVEDC/RESTOREDC — coordinates
in text/shape records are pre-transform. Ignore transform state and geometry is
wrong.
Keep a small transform state: current 2×3 matrix, push/pop with the DC stack, apply to every extracted coordinate. The reference does exactly that. Engine usage is simple (scale/translate only; no rotation in report pages), but it must be applied.
WMF handles are implicit free-slot allocation. EMF create-records carry an explicit
ihObject index — bookkeeping is a straight array. Guard:
SELECTOBJECT of a stock object sets
ENHMETA_STOCK_OBJECT (high bit) — mask and skip the table lookup or you
index garbage.
Band start/end, control start/end (names + text), image names, and the extended-attribute channel on start-control payloads still use the same block structures. Only the carrier changed:
Escape(MFCOMMENT)EMR_GDICOMMENT
Comment payload starts at byte 13 of the record (1-based; after
iType/nSize/cbData) with the same MFCOMMENT magic
(0527h). Block layouts are unchanged; fixed offsets shift by the carrier
prefix delta. Reference has per-kind offsets.
EMR_GDICOMMENT. Walk
blocks inside the record, stride by each block's size (fixed kinds fixed;
start-control / image-name / comment-info = struct size + embedded string length) until
cbData is consumed. Today's engine usually emits one block per record — a
one-block parser will pass local tests and fail later when GDI merges. Walk blocks.
EMR_STRETCHDIBITS carries a packed DIB (BITMAPINFO
immediately followed by bits). DIB starts at RecordStart + offBmiSrc.
Pipelines that re-read image bytes by file offset should publish that offset. bmi+bits
contiguity is verified on engine output.
EMR_ALPHABLEND (§6h) carries the same packed-DIB layout: BITMAPINFO at
RecordStart + offBmiSrc, bits at RecordStart + offBitsSrc
(publish both — the shipped parser passes the bits offset as BITS= to
ProcessImageW), 32 bpp BGRA, the BLENDFUNCTION in
dwRop (SourceConstantAlpha = byte 2, AlphaFormat = byte 3;
AC_SRC_ALPHA = per-pixel, premultiplied). Positive biHeight =
bottom-up rows.
EXTTEXTOUTW strings are UTF-16LE code units. For store/export/measure/
truncate logic, read the tester guide §2 (UTF-16 code units, BMP vs non-BMP):
one unit is not always one character; non-BMP (many emoji, supplementary CJK) is a
surrogate pair (two units) — keep pairs intact at any cut. ANSI output:
narrow deliberately (best-fit, lossy). UTF-8/UTF-16 output: carry units through.
SETWINDOWORG(-offset)) — paper-origin
coords.szlMillimeters is integer-rounded by GDI — raw
pixels/mm DPI is slightly off (e.g. 598.71 vs true 600).
Working recipe: derive DPI from the header, snap to nearest
multiple of 4 (real drivers are; guard ≤2% deviation), reconstruct physical
offset as the centered difference between rclFrame (0.01 mm —
×1000/2540 → 1/1000 inch) and the printable area. Exact for centered printable areas
(the norm); bounded approx for asymmetric margins. For page sizing use
rclFrame, never rclBounds (tight ink extent in recording
pixels — mis-sizes the page).
Tools that only play into a DC (§8) can ignore most of this — transforms resolve themselves. The law matters when extracting geometry (layout, export coords, hit-testing).
(Beta refresh after 08-10-2026 — pages from earlier beta builds recorded emoji-bearing strings differently; the contract below is the shipped one.)
A string containing no emoji records as one ordinary visible
EXTTEXTOUTW — nothing changes. A string that contains emoji
records as a partitioned sequence inside its control:
ETO_OPAQUE record carrying the background fill.EXTTEXTOUTW records —
real text color, transparent background, UTF-16 units verbatim (any script, not just
ASCII).EMR_ALPHABLEND color raster stamp
only — no text glyph is drawn for it.EXTTEXTOUTW record — complete text, real
color, original position — bracketed by an empty clip
(SAVEDC + INTERSECTCLIPRECT(0,0,0,0) + RESTOREDC),
so playback draws nothing from it.
Example: u'Ω中 😀 café' records Ω中 (3 units) + stamp +
café + the clipped 10-unit full string.
What this means per path:
IReportGeneratorW.ProcessImageW after the owning control's
ProcessStringW (the control paints its own background first). Place it
as an image with its alpha as a soft mask, and draw the emoji's own surrogate pair
invisibly — the shipped PDF target uses text render mode 3, so the text stays
searchable — otherwise the pair renders as two notdef boxes under the picture.ETO_OPAQUE fill makes GDI
emit three EMR_EXTTEXTOUTW records (the fill plus two
zero-char ETO_OPAQUE|ETO_GLYPH_INDEX artifacts) — don't pin counts to one
record per drawn string.Without DirectWrite on the machine the stamps are absent and emoji record as ordinary (monochrome) text — the pre-refresh form.
cmap table still
travels whole. Any narrow-only target — shipped or third-party — fed wide content
that loses characters posts one “Report Export Notice” per document;
all-ASCII content stays silent.UNICODE is opt-in. Non-opted
reports unchanged. If a product is not EMF-ready, shared customers should not opt
reports that flow through it yet — attribute is per-report; both styles coexist in
one app.GetEnhMetaFile + PlayEnhMetaFile into
a DC — full page (text, transforms, images, colors). Same path the built-in previewer
and printing use.Image::FromFile / FromStream
auto-detects EMF; DrawImage renders. For GDI+/.NET pipelines, an
.emf page is just an image. (Shipped PNG export is this path — no format
work.)
Size the target rect from rclFrame at display DPI (§6g). Keep the WMF path
for classic pages behind the §2 header check — one page-level branch covers both eras.
PageTextIndexClass (LIBSRC ABPREVIEW.INC/.CLW) is the per-page text index the new ReportPreviewClass searches with, and it is GUI-free: a Path A consumer can adopt it as-is instead of walking pages itself. It is the shipped page parser (WMFDocumentParser) driven with a no-op generator that implements both IReportGenerator faces, so every text-bearing control arrives ONCE per control as {text (UTF-16 units on an EMF page, narrow bytes on a WMF page), rectangle in 1/1000 inch paper-absolute, font}; Find(needle, caseSensitive) folds with UPPER on both sides and keeps surrogate pairs whole. Three laws the previewer had to learn, for anyone overlaying a played page:
rclFrame vs szlDevice/szlMillimeters from the page header and subtract the centred difference (the index keeps it as OffLeft/OffTop; WMF pages bake it in = 0).Pos is the control’s clip box, not the glyph extent — a hit box that hugs the words is measured with the run’s font (GetTextExtentPoint32W at 20 px/pt on a screen DC → mils).SetWindowPos(HWND_TOP) and give the image WS_CLIPSIBLINGS. The RTL posts no scroll events for an IMAGE (wheel or scroll bar): an overlay watches PROP:VScrollPos/HScrollPos itself.Known floor: a CHECK caption on a REPORT,UNICODE page is recorded narrowed by the engine (Ω → O); STRING captions are wide. A viewer can now offer what the shipped previewer offers: search with highlighted hits, hit counts per page, marks, print/export marked (export = hand the parser a filtered COPY of the preview queue; the print path prunes).
Compile time (12.0.26): the project define report_unicode=>1 (or PRAGMA('define(report_unicode=>on)') at the top of a module) gives every REPORT the UNICODE attribute as it compiles - the switch for a hand-coded application or a report library that never meets the templates; one report narrows itself with Report{PROP:Unicode} = False before its first PRINT.
Slice 6 (12.0.26): the shipped previewer builds its index on a thread (StartIndexing / TakeIndexed, the ABDOCK START + NOTIFY shape) and copies the runs under a Shift+drag band (CopyRuns: the inverse of the highlight mapping, reading order, wide to the clipboard) - both are VIRTUAL, a viewer of its own can reuse them.
IReportGeneratorW, zero-break); what we want to hear now is adoption
intent: would your product adopt it, and on which targets first?,UNICODE to
any REPORT (all-ANSI is fine — output should match the classic twin), push pages
through the pipeline. Repeat with real wide data (USTRING with mixed script + emoji
covers the matrix).U+0041 U+1F600 …) beat
mojibake screenshots.Third-party report tools are why the feature is opt-in per report, not a format flag day. Goal: transition time spent on product-specific code, not rediscovering EMF traps. Detail is either in this note or in the shipped parser source — ask for anything that is neither.