mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-12 08:52:32 +00:00
* metafile: hold a placed pen width to what covers the frame Picture::place scaled a pen width by the fit with no upper bound, so a pen of 1e30 units made a renderer flatten round caps of that size. A placed pen is now at most twice the diagonal of the frame and the path together, which already covers the whole frame. Ported from storytold/wordcraft#5427. * metafile: one byte budget for items, EMF+ objects and clips; count clip regions when placing - Canvas::shade_shape returns when the canvas is full or the clip is empty, instead of sampling a tile that is then dropped. - EMF+ objects, the pieces of a continued object and the current and saved EMF+ clips count against the canvas's 256 MiB budget, in both directions: an object is not stored past what the canvas holds, and the canvas stops at what the objects hold. - A nested metafile held by an EMF+ image object is charged for what its items own (segments, pixels, text, clip rectangles; a shared tile or clip path once) rather than 128 bytes an item. - Picture::place charges each clip region it expands to the 500,000 element budget, for every kind of item, and expands a region once for the consecutive items that share it. Ported from storytold/deckcraft#1777, with its review comments addressed (the canvas checks include the held bytes; text and bitmap items are checked too; regions are matched by content, not by address). * metafile: a negative crop side pads the picture crop_window took each side as 0..1, so a negative side (a negative a:srcRect side in an Office file, or a picture fitted whole into a frame of another shape) was dropped and the picture was stretched over its frame. A side is now taken down to -100: the window starts before the source or ends past it, and Picture::fit places the source inside the frame at its own proportions. Ported from storytold/wordcraft#2027 (the -100 bound and the invariant test) and storytold/wordcraft#5206 (the padding test). * metafile: decode 8-bit text in the font's code page 8-bit text (WMF TEXTOUT and EXTTEXTOUT, EMF EXTTEXTOUTA and SMALLTEXTOUT) was decoded in Windows-1250, 1251 or 1252 only, and a double-byte character became a blank. - Greek, Turkish, Vietnamese, Hebrew, Arabic, Baltic and Thai character sets decode in their code pages (encoding_rs, already a workspace dependency). - Shift_JIS, UHC, GBK and Big5 pairs decode to their character, with the lead and trail ranges of each set; the character keeps the advances of both bytes. A lead byte without a trail byte is one undefined byte. - A WMF face name is read in the font's code page. - The Symbol font selected with DEFAULT_CHARSET keeps its bytes as symbol codes (U+F000 + code), as SYMBOL_CHARSET fonts do. Ported from storytold/wordcraft#5187 (the code pages, the double-byte ranges, the face name and the Symbol font) and storytold/deckcraft#792 (the Hebrew and Arabic tests). * metafile: dual EMF+ files draw their EMF+ records, as GDI+ does For a dual EMF+ file the reader played the plain EMF records and ignored the EMF+ ones. Office draws metafiles with GDI+, which does the opposite, and a file's GDI fallback can be incomplete (the EMF+ part draws a nested metafile the fallback lacks). EMF+ records now always play. In a dual file the plain EMF records are skipped, except between an EmfPlusGetDC record and the next EMF+ record. EMF+ only files and plain EMF files are read as before. Ported from storytold/deckcraft#1464. --------- Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com> Co-authored-by: David Lin <woei66@gmail.com> Co-authored-by: Harrison Booth <73434116+Harrison-Booth@users.noreply.github.com>
217 lines
18 KiB
Markdown
217 lines
18 KiB
Markdown
# craft-metafile
|
|
|
|
Windows metafiles for the Craft apps: one reader for WMF, EMF and EMF+, and writers for EMF and WMF.
|
|
|
|
- `parse(bytes) -> Picture` plays a file's records on a model of the GDI device context and
|
|
returns what was drawn: paths, pattern fills, bitmaps and text, each with its clip.
|
|
- `size(bytes)` gives the picture's size from the header alone when the header decides it.
|
|
- `sniff(bytes) -> Option<Kind>` and `is_metafile(bytes)` recognise the formats.
|
|
- `Picture::fit` and `Picture::place` fit a picture (with crop fractions) into a rectangle for
|
|
callers that draw it; `crop_window` is the crop rule they share with bitmaps (a negative crop
|
|
side pads the picture).
|
|
- `write::emf` and `write::wmf` (feature `write`) write a list of drawing operations.
|
|
|
|
The crate depends on `kurbo`, `thiserror`, `encoding_rs` (the code pages of 8-bit text) and `image`
|
|
(PNG, JPEG, GIF, TIFF and BMP decoders, for the image files that EMF+ and `BI_JPEG`/`BI_PNG`
|
|
bitmaps carry). It knows no fonts, no file system
|
|
and no application.
|
|
|
|
## The model
|
|
|
|
A `Picture` has a size (points when the file states one: the EMF frame, the placeable WMF box;
|
|
device or logical units otherwise) and `items` in paint order, in `0..width` by `0..height`, y down:
|
|
|
|
| Item | What it holds |
|
|
|---|---|
|
|
| `Item::Path` | Segments (`Seg`: move, line, cubic, close), a fill colour, a stroke colour and width (0 is a hairline), the stroke's `LineStyle` (cap, join, miter limit, dashes), the fill rule. |
|
|
| `Item::Pattern` | A path filled with a repeating RGBA `Tile`: hatch and bitmap brushes, EMF+ gradients and textures. Fills with the same brush share one tile. |
|
|
| `Item::Bitmap` | RGBA8 pixels (straight alpha, rows top-down), the rectangle they fill and a `transform` that also holds a turn or a skew (then the rectangle is the box around it). |
|
|
| `Item::Text` | The string, the font the file asks for (family, height, weight, italic, underline, strike, character set), per-character advances, colours, alignment and a transform from text space. |
|
|
|
|
Every item carries the clip it was drawn under: `region` (the GDI clip region as rectangles) and
|
|
`clips` (clip paths from `SELECTCLIPPATH`, outermost first; items under the same clip share one
|
|
`Arc<ClipPath>`). An item shows inside its region and inside each of its clip paths.
|
|
|
|
`Picture::skipped_records` counts records of a type the reader does not play, so a caller can say
|
|
that part of a file was not read.
|
|
|
|
### Why this model
|
|
|
|
The three apps had two output models. WordCraft's and DeckCraft's readers produced a picture of
|
|
paths, bitmaps and text to draw. VectorCraft's reader built VectorCraft document nodes directly,
|
|
and kept what an editor needs and a renderer can lose: the pen's caps, joins and dashes, clip
|
|
paths as clips, and the turn of an image. The shared model is the picture, extended with exactly
|
|
those three things (`LineStyle`, `ClipPath`, `Item::Bitmap::transform`), so an editor can rebuild
|
|
objects from it and a renderer can draw it. Bitmaps are decoded RGBA rather than raw DIB bytes
|
|
because every caller needs pixels (two draw them, one re-encodes them as PNG), and because the
|
|
decoder is where the size, pixel and work limits have to be enforced.
|
|
|
|
## Records read
|
|
|
|
### WMF (MS-WMF)
|
|
|
|
| Group | Records |
|
|
|---|---|
|
|
| Headers | Placeable header, memory and disk metafile headers. |
|
|
| Mapping | `SETMAPMODE`, `SETWINDOWORG`, `SETWINDOWEXT`, `SETVIEWPORTORG`, `SETVIEWPORTEXT`, all map modes (text, metric, isotropic, anisotropic). A placeable file is played into its box the way Office plays it. |
|
|
| State | `SAVEDC`, `RESTOREDC`, `SETPOLYFILLMODE`, `SETROP2`, `SETBKMODE`, `SETBKCOLOR`, `SETTEXTCOLOR`, `SETTEXTALIGN`. |
|
|
| Objects | `CREATEPENINDIRECT` (style, cap and join bits), `CREATEBRUSHINDIRECT` (solid, null, hatched), `DIBCREATEPATTERNBRUSH`, `CREATEFONTINDIRECT`, `SELECTOBJECT`, `DELETEOBJECT`, stock objects. `CREATEPALETTE`, `CREATEPATTERNBRUSH` and `CREATEREGION` take their handle slot. |
|
|
| Drawing | `MOVETO`, `LINETO`, `POLYLINE`, `POLYGON`, `POLYPOLYGON`, `RECTANGLE`, `ROUNDRECT`, `ELLIPSE`, `ARC`, `PIE`, `CHORD`, `PATBLT`. |
|
|
| Bitmaps | `STRETCHDIB`, `DIBSTRETCHBLT`, `DIBBITBLT` (with or without a source). |
|
|
| Text | `EXTTEXTOUT` (rectangle, opaque, clipped, dx), `TEXTOUT`, decoded in the selected font's character set: code pages 1250 to 1258 and 874, Shift_JIS, UHC, GBK and Big5 (a double-byte character takes the advances of both its bytes), and the symbol character set (also the Symbol font under `DEFAULT_CHARSET`), whose codes stay in the private-use area at U+F000. Face names are read in the font's code page. Johab pairs are blanks. |
|
|
| Clipping | `INTERSECTCLIPRECT`, `EXCLUDECLIPRECT`. |
|
|
| Not read | `OFFSETWINDOWORG`, `OFFSETVIEWPORTORG`, `SCALEWINDOWEXT`, `SCALEVIEWPORTEXT`, flood fills, `SETPIXEL`, region records (`FILLREGION`, `FRAMEREGION`, `PAINTREGION`, `INVERTREGION`, `SELECTCLIPREGION`, `OFFSETCLIPRGN`), the device-dependent `BITBLT`, `STRETCHBLT` and `SETDIBTODEV`. They are counted in `skipped_records`. |
|
|
|
|
### EMF (MS-EMF)
|
|
|
|
| Group | Records |
|
|
|---|---|
|
|
| Header | Frame, device size in pixels, millimetres and (when present) micrometres. |
|
|
| Mapping | `SETMAPMODE`, window and viewport origin and extent, `SCALEVIEWPORTEXTEX`, `SCALEWINDOWEXTEX`, `SETWORLDTRANSFORM`, `MODIFYWORLDTRANSFORM`. |
|
|
| State | `SAVEDC`, `RESTOREDC`, `SETPOLYFILLMODE`, `SETROP2`, `SETBKMODE`, `SETBKCOLOR`, `SETTEXTCOLOR`, `SETTEXTALIGN`, `SETMITERLIMIT`, `SETARCDIRECTION`. |
|
|
| Objects | `CREATEPEN`, `EXTCREATEPEN` (cosmetic and geometric, cap, join, user-style dashes), `CREATEBRUSHINDIRECT`, `CREATEMONOBRUSH`, `CREATEDIBPATTERNBRUSHPT`, `EXTCREATEFONTINDIRECTW`, `SELECTOBJECT`, `DELETEOBJECT`, stock objects. |
|
|
| Drawing | `MOVETOEX`, `LINETO`, `POLYLINE(16)`, `POLYLINETO(16)`, `POLYGON(16)`, `POLYBEZIER(16)`, `POLYBEZIERTO(16)`, `POLYPOLYLINE(16)`, `POLYPOLYGON(16)`, `RECTANGLE`, `ROUNDRECT`, `ELLIPSE`, `ARC`, `CHORD`, `PIE`. |
|
|
| Paths | `BEGINPATH`, `ENDPATH`, `CLOSEFIGURE`, `FILLPATH`, `STROKEPATH`, `STROKEANDFILLPATH`, `ABORTPATH`, `SELECTCLIPPATH` (`RGN_AND`, `RGN_COPY`). |
|
|
| Bitmaps | `BITBLT`, `STRETCHBLT`, `STRETCHDIBITS`, `SETDIBITSTODEVICE`, `ALPHABLEND` (constant and per-pixel alpha); source-less raster operations fill with the brush, black or white; `SRCAND`, `SRCPAINT` and `SRCINVERT` are drawn as they look over white. |
|
|
| Text | `EXTTEXTOUTA`, `EXTTEXTOUTW`, `SMALLTEXTOUT`; 8-bit strings are decoded as WMF text is; glyph-index strings are left out. |
|
|
| Clipping | `INTERSECTCLIPRECT`, `EXCLUDECLIPRECT`, `EXTSELECTCLIPRGN` (all five modes, on rectangles), `SELECTCLIPPATH`. |
|
|
| Not read | `ANGLEARC`, `ARCTO`, `POLYDRAW(16)`, `SETPIXELV`, flood fills, region paints, `MASKBLT`, `PLGBLT`, `TRANSPARENTBLT`, `GRADIENTFILL`, `POLYTEXTOUT`, `WIDENPATH`, `SETMETARGN`, `OFFSETCLIPRGN`, `SETLAYOUT`, `SELECTCLIPPATH` with `RGN_OR`, `RGN_XOR` or `RGN_DIFF` (the clip stays as it was). Unknown types are counted in `skipped_records`. |
|
|
|
|
### EMF+ (MS-EMFPLUS)
|
|
|
|
A file with EMF+ records is played from them, as GDI+ plays it. In a dual file (one that also
|
|
draws the picture with plain EMF records, as a fallback for GDI) the plain records are skipped,
|
|
except those between an `EmfPlusGetDC` record and the next EMF+ record. What the EMF+ reader does
|
|
not read (the table's last row) is therefore lost in a dual file even where its EMF records carry it.
|
|
|
|
| Feature | Read |
|
|
|---|---|
|
|
| Objects | Brushes (solid, hatch, texture, linear gradient with preset colours or blend factors, path gradient), pens (colour and width), paths, regions, images, fonts, string formats; objects continued over several records. |
|
|
| Drawing | `FillRects`, `DrawRects`, `FillPolygon`, `DrawLines`, `FillEllipse`, `DrawEllipse`, `FillPie`, `DrawPie`, `DrawArc`, `FillPath`, `DrawPath`, `DrawBeziers`, `DrawCurve`, `DrawClosedCurve`, `FillClosedCurve`, `FillRegion`, `Clear`. |
|
|
| Images | `DrawImage`, `DrawImagePoints`: raw pixel formats, PNG, JPEG, GIF, TIFF and BMP payloads, and nested metafiles (three levels). |
|
|
| Text | `DrawString` (alignment, wrapping to the layout width), `DrawDriverString` with character codes, and with glyph indices in the Windows core fonts. |
|
|
| State | World and page transforms, `Save`, `Restore`, containers. |
|
|
| Clipping | `SetClipRect`, `SetClipPath`, `SetClipRegion`, `ResetClip`, `OffsetClip`: rectangles and convex shapes cut the drawing itself; other shapes clip to their bounding rectangle; region nodes combine by intersect and union. |
|
|
| Not read | Pen caps, joins and dashes (EMF+ strokes have the default line style), image attributes and effects, glyph indices of other fonts, region XOR, exclude and complement, rendering hints, `SetRenderingOrigin`, the terminal-server records. |
|
|
|
|
### Bitmaps (DIB)
|
|
|
|
`BITMAPINFOHEADER` and larger headers at 1, 4, 8, 16, 24 and 32 bits with `BI_RGB` or
|
|
`BI_BITFIELDS`; `BITMAPCOREHEADER` at 1, 4, 8 and 24 bits; `BI_JPEG` and `BI_PNG`. Bottom-up and
|
|
top-down rows, source rectangles (only the rows and columns a record draws are decoded), mirrored
|
|
destinations. RLE compression is not decoded.
|
|
|
|
## Writers (feature `write`)
|
|
|
|
`write::emf(ops, region, app, title)` and `write::wmf(ops, region)` take `write::Op`s in points,
|
|
y down, in paint order: `Fill` (path, fill rule, colour), `Stroke` (path and `Pen`: colour, width,
|
|
cap, join, miter limit, dashes), `Image` (RGBA pixels, an affine placement, an opacity), `Clip`
|
|
and `Unclip`. `region` is the part of that space the picture covers.
|
|
|
|
- EMF keeps curves, writes geometric pens (`EXTCREATEPEN`; dashes as `PS_USERSTYLE` when the pen
|
|
can draw them, else one line per dash), path clips inside `SAVEDC`/`RESTOREDC`, opaque images as
|
|
`STRETCHDIBITS` and translucent ones as `ALPHABLEND`, each under a world transform.
|
|
- WMF flattens curves, writes 16-bit coordinates behind a placeable header (at most 1440 units an
|
|
inch, fewer for large pictures), and returns what it could not hold in `write::Wmf`
|
|
(`translucent_images`, `turned_images`, `clips_ignored`, `oversized_images`).
|
|
|
|
The reader reads back what the writers write; the tests round-trip both formats.
|
|
|
|
## Limits
|
|
|
|
Every size, count and offset from a file is checked before it is used, with `get` and checked
|
|
arithmetic. Past a limit the reader keeps what it has (a shorter picture) or leaves the item out;
|
|
it never panics and never allocates from an unchecked count. Each limit has a test.
|
|
|
|
| Limit | Value | Past it |
|
|
|---|---|---|
|
|
| Items in a picture | 200,000 | The parse stops. |
|
|
| Bytes of path points and pixels kept (pattern brushes, clip paths, EMF+ objects with the nested pictures they hold, and EMF+ clips included) | 256 MiB | The parse stops; an EMF+ object past it is not stored. |
|
|
| Points in one path bracket; points in one record | 2,000,000 | The bracket stops growing; the record is left out. |
|
|
| Object table slots | 4,096 | The object is not stored. |
|
|
| `SAVEDC` depth | 1,024 | The parse stops. |
|
|
| Clip paths nested on one device context | 32 | The parse stops. |
|
|
| Rectangles in a clip region | 64 | Further rectangles are dropped (this can only hide drawing). |
|
|
| Pen style entries | 16 | Further entries are dropped. |
|
|
| Pixels in one bitmap | 40,000,000 | The bitmap is left out. |
|
|
| Bitmap side drawn | 30,000 | The bitmap is left out before it is decoded. |
|
|
| JPEG or PNG side | 32,768 | The image is left out before it is decoded. |
|
|
| JPEG and PNG pixels decoded in one file | 2^28 | Further images are left out before they are decoded. |
|
|
| Pattern tile side | 256 | The brush is not made. |
|
|
| Characters in one string | 16,384 (EMF+: 32,768) | The string is cut. |
|
|
| EMF+ objects, save stack | 64, 64 | Ignored. |
|
|
| EMF+ points in one record; region nodes | 100,000 | The list is cut; the region is not made. |
|
|
| EMF+ region depth | 32 | The region is not made. |
|
|
| EMF+ gradient stops | 256 | The count is cut. |
|
|
| EMF+ continued object | 64 MiB | The size is cut; nothing is reserved ahead. |
|
|
| Nested metafiles | 3 levels | The inner picture is not read. |
|
|
| Clipping work per item, and per clip area | 1,000,000 and 4,000,000 steps | The item is left out; the clip keeps its previous area. |
|
|
| Masking a bitmap to a clip that is not a rectangle | 5,000,000 point tests | The bitmap is left out. |
|
|
| Placed path elements, clip regions included (`Picture::place`) | 500,000 | Placing stops. |
|
|
| Placed coordinates (`Picture::place`) | ±1,000,000 | The item is left out. |
|
|
| Placed pen width (`Picture::place`) | Twice the diagonal of the frame and the path together | The width is held there. |
|
|
| Writers: dashes cut from one stroke | 100,000 | The stroke is written solid. |
|
|
| Writers: segments from one curve | about 4,096 | The tolerance is raised. |
|
|
|
|
The tests parse every prefix of sample files, files with each byte replaced, and random bytes.
|
|
|
|
## What stays in the apps
|
|
|
|
- Mapping a `Picture` onto the app's document: VectorCraft builds paths, clipping groups, images
|
|
and point type from the items (joining consecutive lines into one path, dropping a clip that
|
|
covers the whole frame, turning skipped records and hatch and pattern fills into its warnings);
|
|
WordCraft and DeckCraft keep a picture as a picture.
|
|
- Drawing: rasterising and PDF output of placed items, font lookup, shaping and glyph drawing
|
|
(`Text` only names a font), caching of parsed pictures.
|
|
- For the writers: walking the document into `write::Op`s. Gradients, pattern fills, type as
|
|
outlines, stroke alignment, width profiles, arrowheads, effects and opacity are decided there,
|
|
as is scaling an image down before it is written and the wording of any warning.
|
|
|
|
## Origin
|
|
|
|
| Part | From |
|
|
|---|---|
|
|
| The reader (`canvas`, `emf`, `wmf`, `geom`, `place`, `bytes`) | DeckCraft `crates/metafile` at `cf0d7fbb`, itself ported from WordCraft at `05679497`. |
|
|
| EMF+ (`emfplus`), clip regions (`clip`, `cliprect`), text (`text`), pattern and hatch brushes | DeckCraft. |
|
|
| The cropped, depth-once DIB decoder (`dib`), the bitmap side cap, `size`, `crop_window`, the free-slot object table, capacity-based budget charging, the exact reservation for poly-polygon groups | WordCraft `crates/metafile` at `639b8133`, merged three ways over the common ancestor. |
|
|
| `LineStyle`, clip paths, `Item::Bitmap::transform`, `skipped_records`, the records behind them (`EXTCREATEPEN` entries, `SETMITERLIMIT`, `SETARCDIRECTION`, `SCALEVIEWPORTEXTEX`, `SCALEWINDOWEXTEX`, `SELECTCLIPPATH`, `ABORTPATH`), closing figures in `STROKEANDFILLPATH`, the micrometre device size, core-header, JPEG and PNG bitmaps, the per-file decode budget | VectorCraft `crates/metafile` at `3deafc24` (its reader's behaviour, written anew on this model). |
|
|
| The writers (`write`) | VectorCraft `crates/metafile` (`emf.rs`, `wmf.rs`, the encoding half of `dib.rs`, `vectorcraft_geom::flatten`). |
|
|
|
|
All three sources are MIT OR Apache-2.0. The tests build every file they read in code; the crate
|
|
bundles no sample files.
|
|
|
|
## Migration
|
|
|
|
### DeckCraft (`deckcraft-metafile`)
|
|
|
|
| Before | After |
|
|
|---|---|
|
|
| `deckcraft_metafile::{parse, is_metafile, Picture, Item, Seg, Tile, Text, Font, HAlign, VAlign, SYMBOL_CHARSET, PlacedItem, MAX_BITMAP_SIDE, Error}` | The same names in `craft_metafile`. |
|
|
| `Item::Path { segs, fill, stroke, even_odd, region }` | Adds `line` and `clips`. Draw `line` when stroking; clip to each of `clips` as to `region`. |
|
|
| `Item::Bitmap { rect, .. }`, `Item::Pattern { .. }`, `Text` | Add `clips`; `Item::Bitmap` adds `transform` (equal to `rect` unless the bitmap is turned). |
|
|
| `PlacedItem::*` | Add `clips` (`PlacedClip`: a path and its fill rule); `Path` adds `line`; `Bitmap` adds `transform`. |
|
|
| `Picture { width, height, items }` | Adds `skipped_records`. |
|
|
| `Picture::fit` with crops clamped to 95% a side | `crop_window`: each axis keeps at least 0.5%. |
|
|
|
|
### WordCraft (`wordcraft-metafile`)
|
|
|
|
| Before | After |
|
|
|---|---|
|
|
| `parse`, `size`, `is_metafile`, `Picture`, `Seg`, `Error`, `MAX_BITMAP_SIDE`, `crop_window`, `Picture::{has_size, fit, place}`, `Item::take_pixels` | The same names in `craft_metafile`. |
|
|
| `Item::Path`, `Item::Bitmap` | Add `region`, `clips`, `line`, `transform` as above; new `Item::Pattern` (hatch and pattern brushes now draw). |
|
|
| `Text { text, x, y, size, cell, font, bold, italic, color, angle, halign, valign, dx }` | `Text { transform, text, dx, font: Font { family, height, weight, italic, .. }, color, background, h_align, v_align, clip, region, clips }`. The reference point and angle are in `transform`; `size` is `font.height.abs()` in text units and `cell` is `font.height > 0`; `bold` is `font.weight >= 600`. A height of 0 and an empty family are the file's "default font": WordCraft's 16-unit Arial default moves to WordCraft. |
|
|
| `Text::layout`, `TextLayout` | Stay in WordCraft, over the shared `Text`. |
|
|
| `PlacedItem::Text { index, tf }` | `PlacedItem::Text { index, transform, clip, region, clips }`. |
|
|
| Text from `EXTTEXTOUTW` only | Also `EXTTEXTOUTA`, `SMALLTEXTOUT`, WMF text, opaque backgrounds, EMF+ strings. |
|
|
|
|
### VectorCraft (`vectorcraft-metafile`)
|
|
|
|
| Before | After |
|
|
|---|---|
|
|
| `sniff`, `Kind` | `craft_metafile::{sniff, Kind}`. |
|
|
| `import(bytes) -> Imported { document, kind, warnings }` | `craft_metafile::parse(bytes)`, then VectorCraft's own mapping of `Picture` to a `Document`: `Item::Path` to a path node (`line` to cap, join, miter limit and dash), `clips` to clipping groups (`Arc::ptr_eq` groups items), `region` to a rectangle clip, `Item::Bitmap` to an image placed by `transform`, `Item::Text` to point type, `Item::Pattern` to its tile or a flat colour, `skipped_records` to the warning. |
|
|
| `export(doc, region, kind) -> Output` | VectorCraft's `scene` walk produces `craft_metafile::write::Op`s, then `write::emf(&ops, region, "VectorCraft", &doc.title)` or `write::wmf(&ops, region)`; `Wmf`'s flags become the two WMF warnings. |
|
|
| `scene::Pen { cap: LineCap, join: LineJoin, dash: Option<Dash> }` | `write::Pen { cap: Cap, join: Join, dash: Option<write::Dash> }` (`Cap::Flat` is `LineCap::Butt`). A dash with `align_corners`, or one `Dash::emf_pen_draws` refuses, is cut by VectorCraft and passed as the path with no dash, to keep its own dash cutting. |
|
|
| `dib::Rgba::fitted`, `to_png` | Stay in VectorCraft. |
|
|
| EMF+ only files: a warning and nothing imported | They are read. |
|