/** * Simulates Apple's nanov2 allocator for the 80-byte size class. * * OfficeImport uses [NSValue valueWithNonretainedObject:] to cache rendered * elements by OADDrawable pointer. When a slide is freed and the next slide's * drawables land at the same addresses (malloc reuse), the stale cache entry * produces "bleed" — ghost PDF images from earlier slides. * * nanov2 blocks are 16 KB, each holding 204 fixed-size 80-byte slots. * The free list is a per-block LIFO singly-linked stack (from libmalloc * nanov2_malloc.c, confirmed via disassembly + malloc_logger tracing). * * Source: apple-oss-distributions/libmalloc, tag libmalloc-792.80.2 * - nanov2_malloc.c: nanov2_allocate_from_block_inline (line 2513) * - nanov2_malloc.c: nanov2_free_to_block_inline (line 3101) * * RE findings on OfficeImport's PX parser allocation patterns: * * Size classes (from ObjC header ivar analysis): * - OADShape: 72-80 bytes → 80-byte nanov2 class ✓ * - OADImage: 80 bytes → 80-byte class ✓ * - OADConnector: 72-80 bytes (subclass of OADShape) → 80-byte class ✓ * - OADShapeProperties: ~137 bytes → 144-byte class (NOT 80-byte) * - OADGraphicProperties: ~136 bytes → NOT 80-byte * - OADDrawableProperties (base): 80 bytes, but shapes use OADShapeProperties * - OADOrientedBounds: ~60 bytes → 64-byte class * - OADTextBody: ~32 bytes → 32/48-byte class * - OADTextBodyProperties: ~157 bytes → NOT 80-byte * - OADOuterShadowEffect: ~64 bytes → 64-byte class (NOT 80-byte) * - OADPresetShapeGeometry: ~28 bytes → 32-byte class * - OADGroup: ~88 bytes → 96-byte class (NOT 80-byte) * - OADTable: 64 bytes → 64-byte class * * Conclusion: ONLY OADShape/OADImage/OADConnector objects reside in the * 80-byte nanov2 pool. Each drawable = exactly 1 allocation in this pool. * No sub-objects share the same size class. * * Progressive reading pipeline (from PMTop / OIProgressiveReaderDelegate): * 1. PXReader reads slide N's XML → allocates OADDrawable objects * 2. PMTop.readerDidReadElement: → maps slide N (cache check + store) * 3. PDSlide.doneWithContent → frees slide N's drawables (mDrawables = nil) * 4. Freed addresses go back to nanov2 LIFO pool * 5. Repeat for slide N+1: new drawables reuse freed addresses * * LIFO correspondence verified empirically: * For immediate-predecessor bleeds, targetAllocPos maps to * srcAllocPos = prevSlideSize - 1 - targetAllocPos (pure LIFO). * This was confirmed for slides 5←4, 6←5, 7←6, 9←8, 17←16. * * Cache mechanism (CMArchiveManager.mDrawableCache): * - NSMutableDictionary keyed by [NSValue valueWithNonretainedObject:drawable] * - cachedPathForDrawable: checked for EVERY drawable before rendering * - addResourceForDrawable:withType:drawable: stores entry for PDF attachments * - Only PDF-rendered shapes create cache entries (sources) * - ALL drawable types can be cache hit targets (CSS rects included) * * Known limitations of this simulation: * - Predicts ~167 bleeds vs 89 ground truth for the test file * - False positives occur because we can't model every object in the 80-byte * pool (system objects like NSMutableArray internals may interleave) * - Cross-slide bleeds from older slides sometimes have wrong addresses * - Use ql-bleed.ts extraction as verification/fallback */ /** * Simulates the nanov2 80-byte allocator across an entire presentation. * * Each OADDrawable (OADShape/OADImage/OADConnector) = 1 allocation. * Properties, geometry, effects, text body etc. are in different size classes. */ export declare class NanoAllocator { private blocks; private nextBlockBase; /** Allocate one 80-byte slot, returns virtual address. */ alloc(): number; /** Free one 80-byte slot. */ free(addr: number): void; } /** Tracks one drawable's allocation and metadata. */ export interface DrawableSlot { /** Virtual address (from NanoAllocator). */ addr: number; /** Whether this drawable was rendered as PDF (non-rect geometry). */ isPdf: boolean; /** Index within the slide's drawable array. */ index: number; } /** * Cache entry: the drawableElementCache stores rendered HTML keyed by * [NSValue valueWithNonretainedObject:oadDrawable] — i.e. the raw pointer. * After the drawable is freed, the address becomes stale but stays in the cache. */ export interface CacheEntry { /** The stale virtual address. */ addr: number; /** Source slide index (0-based). */ srcSlide: number; /** Source drawable index within that slide. */ srcIndex: number; /** Target drawable index on the slide receiving bleed (set during collision check). */ targetIndex?: number; /** The cached HTML string — populated during rendering. */ html: string; } /** * Per-drawable allocation info extracted from the PPTX. * * Only OADShape/OADImage/OADConnector objects are in the 80-byte nanov2 pool. * Each drawable = exactly 1 allocation. Properties, geometry, effects, text body * etc. are all in different size classes and do NOT interfere with the 80-byte pool. * * OADTable (graphicFrame) = 64 bytes → different nano size class, excluded. */ export interface DrawableAllocInfo { isPdf: boolean; index: number; /** Drawable type for size class routing. */ type: "sp" | "pic" | "cxnSp" | "graphicFrame"; } /** * Simulates the full OfficeImport drawable lifecycle for bleed prediction. * * Progressive pipeline per slide: * 1. Free previous slide's drawables (NSArray releases in forward index order; * each OADDrawable.dealloc frees self → pushed to LIFO). * 2. Allocate new slide's drawables (1 alloc each from 80-byte pool). * 3. Check the element cache for address collisions (= bleed). * ALL drawable types are checked (CSS rects included). * 4. Cache PDF-rendered drawables' addresses. */ export declare function computeBleedMap(slides: Array>): Map;