InstantPage V2: fall back to 600x300 for zero-dimension map blocks

AI/server-sent .map blocks can arrive with dimensions == 0x0 (the wire
w/h are required Int32, but the sender may put 0; our parse and both
serializers preserve whatever arrives). A zero naturalSize.height hit
instantPageV2MediaFrame's else branch and returned a height-0 frame:
the map collapsed to no space, the caption slid up into it, and the V1
node's pin floated over the caption. A zero-sized MapSnapshotMediaResource
would also make MKMapSnapshotter render nothing.

Substitute PixelDimensions(600, 300) (2:1) whenever width <= 0 ||
height <= 0, feeding effectiveDimensions to BOTH the layout naturalSize
AND the InstantPageMapAttribute so the snapshot resource is non-zero and
actually renders. Scoped to the V2 .map arm; V1 (real web articles)
always carries real dimensions and never trips this.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
isaac 2026-06-02 11:36:18 +02:00
parent 9b1573e87e
commit 071799368d
2 changed files with 17 additions and 2 deletions

View file

@ -74,6 +74,7 @@ Every V2 block-media kind **except `.audio`** lays out **flush** with the bubble
- **Full-width media bleeds `instantPageV2MediaEdgeBleed` (4pt) past the trailing edge.** The pageView sits at `x: -1` inside `containerNode` (a border-hiding hairline), so a frame at `x: 0, width: boundingWidth` falls ~1px short of the container's right rounded-clip edge → a 1px corner notch. A small over-bleed on **full-width** items only (`fillsWidth = scaledSize.width >= availableWidth - 1.0`) closes it; a genuinely small image gets no bleed. **The bleed never widens the bubble** because `layoutInstantPageV2` clamps `contentSize.width = min(maxX, boundingWidth)` (gated by `context.fitToWidth`, which both callers — the rich bubble and the send preview — pass `true`).
- **Captions stay inset.** `layoutCaptionAndCredit` is still called with the page `horizontalInset` and offset by the **un-bled** `scaledSize.height`; the caption/credit text is inset under a full-bleed image. The `isCover && captionHeight > 0` cover-padding block is unchanged.
- **Audio is the lone exception** and routes through the non-flush branch of `instantPageV2MediaFrame` (inset by `horizontalInset`, caller's cornerRadius), reproducing the legacy behavior exactly.
- **`.map` blocks get a 600×300 (2:1) fallback when the sender omits dimensions.** AI/server-sent `.map` blocks can arrive with `dimensions == 0×0` (the wire `w`/`h` are *required* `Int32`, but the sender may put 0; our `pageBlockMap` parse and both serializers — Postbox `sw`/`sh`, FlatBuffers `required dimensions` — preserve whatever arrives, so the zero originates upstream). A zero `naturalSize.height` hits `instantPageV2MediaFrame`'s `else` branch and returns a **height-0** frame: the map collapses to no space, the caption slides up into it, and the V1 node's pin (positioned at `size.height*0.5 10 pinSize/2`) floats over the caption. **The `.map` arm in `InstantPageV2Layout.swift` substitutes `PixelDimensions(600, 300)` whenever `width <= 0 || height <= 0`, and feeds that `effectiveDimensions` to BOTH the layout `naturalSize` AND the `InstantPageMapAttribute`** — the latter is essential because a `MapSnapshotMediaResource(width:0,height:0)` makes `MKMapSnapshotter` render nothing, so fixing only the frame would yield a correctly-sized *blank* box. Real web-article maps (the V1 renderer) always carry real dimensions, so V1 never trips this; the fallback is deliberately scoped to the V2 `.map` arm rather than V1 or the wire/parse layer.
## InstantPage V2 text item height (true font line box)

View file

@ -843,7 +843,21 @@ private func layoutBlock(
horizontalInset: horizontalInset, context: &context)
case let .map(latitude, longitude, zoom, dimensions, caption):
let naturalSize = CGSize(width: CGFloat(dimensions.width), height: CGFloat(dimensions.height))
// AI/server-sent `.map` blocks can arrive with zero `dimensions` (the wire `w`/`h` are
// required, but the sender may put 0). A zero `naturalSize.height` collapses the media
// frame to height 0 (`instantPageV2MediaFrame`'s else branch) the map takes no space,
// the caption slides up into it, and the pin floats over the caption and a zero-sized
// `MapSnapshotMediaResource` makes `MKMapSnapshotter` render nothing. Substitute a sensible
// default (a 2:1 map strip) for BOTH the layout size and the snapshot resource. Real web
// articles (the V1 renderer) always carry real dimensions, so only the rich-message path
// hits this; the fallback is scoped here rather than in V1 or the wire/parse layer.
let effectiveDimensions: PixelDimensions
if dimensions.width > 0 && dimensions.height > 0 {
effectiveDimensions = dimensions
} else {
effectiveDimensions = PixelDimensions(width: 600, height: 300)
}
let naturalSize = CGSize(width: CGFloat(effectiveDimensions.width), height: CGFloat(effectiveDimensions.height))
let map = TelegramMediaMap(
latitude: latitude,
longitude: longitude,
@ -853,7 +867,7 @@ private func layoutBlock(
liveBroadcastingTimeout: nil,
liveProximityNotificationRadius: nil
)
let mapAttributes: [InstantPageImageAttribute] = [InstantPageMapAttribute(zoom: zoom, dimensions: dimensions.cgSize)]
let mapAttributes: [InstantPageImageAttribute] = [InstantPageMapAttribute(zoom: zoom, dimensions: effectiveDimensions.cgSize)]
let mediaIndex = context.mediaIndexCounter
context.mediaIndexCounter += 1
let instantPageMedia = InstantPageMedia(