documentation/docfx/releases/_sources/4.148.0.notes.md
Maintainer-authored. Freeform Markdown — the Polish AI reads this and weaves it into the page (mainly the
## Breaking Changessection), summarizing/migrating rather than pasting it verbatim. It is never machine-overwritten and survives regeneration and re-polish.Scope: 4.148.0 is the first stable v4 line (rolls up the 4.147.x previews), so it lands the bulk of the v4 breaking changes from the Skia m132/m147 upgrade. The items below are the ones the API signature diff cannot show — behavioral changes and interop-struct changes — and so must be documented here by hand. Signature breaks that are in the diff (the
GRVkImageInfo/GrVkYcbcrConversionInforetype + loss of value-equality, and theSKNativeObjectsetter) are summarized fromSkiaSharp/SkiaSharp.breaking.md.
These compile unchanged but behave differently at runtime, so they never appear in a signature diff. Test text layout and stream seeking after upgrading from 3.x.
new SKFont() now has an empty typeface (#3735)Upstream m132 removed the lazy "resolve default at draw time" pattern; the SkFont
constructor now eagerly stores an empty typeface when given none.
Before (3.x): new SKFont().Typeface was null and resolved to the platform default at
draw time — new SKFont().MeasureText("Hello") returned a real width.
After (4.x): the typeface is SKTypeface.Empty (0 glyphs), so MeasureText("Hello")
returns 0 and nothing is drawn. Setting font.Typeface = null likewise yields
SKTypeface.Empty.
Migration: construct with an explicit typeface, and detect "unset" with IsEmpty:
// Before (3.x) — worked via lazy default
var font = new SKFont();
var width = font.MeasureText("Hello"); // > 0
// After (4.x) — pass the typeface you want
var font = new SKFont(SKTypeface.Default);
var width = font.MeasureText("Hello"); // > 0
// Detecting "no typeface set"
bool unset = font.Typeface.IsEmpty; // was: font.Typeface == null
new SKPaint().Typeface returns Default instead of null (#3736)The underlying SkCompatPaint moved from SkFont() (null → lazy default) to
SkFont(MakeEmpty()). SkiaSharp mitigates this so text still works out of the box, but the
null semantics changed.
new SKPaint().Typeface was null (lazy default).SKTypeface.Default (non-null). Text measurement and drawing still
work — SkiaSharp seeds a default font — and SKPaint.Reset() also restores that default.paint.Typeface == null to detect "no typeface
set" no longer fires. Use paint.Typeface.IsEmpty, or drop the check (a usable default is
always present).SKTypeface.FromFamilyName("missing") returns Default, not null, on Android (#3737)The old per-platform legacyMakeTypeface() path (which returned null on Android for an
unknown family) was replaced by SKFontManager.Default.MatchFamily(name, style) ?? Default,
making every platform consistent.
Before (3.x): Android returned null for a missing family; other platforms returned a
fallback face.
After (4.x): all platforms return SKTypeface.Default — FromFamilyName is now never
null.
Migration: for a strict "was the font actually found?" check, use the font manager, which
still returns null on no match:
// Strict null semantics (all platforms)
var tf = SKFontManager.Default.MatchFamily("MissingFont");
if (tf is null) { /* font not found — apply your own fallback */ }
SKTypeface.Default now resolves through the font manager (#3738)SkTypeface::RefDefault()/MakeDefault() were removed upstream in m132. Default resolution
moved to matchFamilyStyle(null, Normal) with an empty-typeface fallback.
sk_typeface_ref_default().SKTypeface.Default is now guaranteed
non-null on every platform, and Android API 36+ (where the manager could return null — the
original #3693) falls back to SKTypeface.Empty instead of crashing.SKStream.Move(long) now throws and is obsolete (#3740)The native sk_stream_move offset is a 32-bit int. The Move(long) overload always did an
unchecked (int) cast, silently truncating large offsets — a latent data-corruption bug.
stream.Move((long)int.MaxValue + 1) silently truncated (wrapping
negative); no warning.checked((int)offset), so out-of-range offsets throw
OverflowException, and the overload is [Obsolete] (compiler warning CS0618).Move(int) with an in-range offset (validate/clamp large values
yourself first). Most callers already pass int-range values and only need to change the call
to silence the obsolete warning.GRVkBackendContextNative is an interop struct outside the public-API surface the diff tool
compares, so its field removals appear in no *.breaking.md — they must be documented here.
GRVkBackendContextNative — five fields removed (#3741)Upstream simplified GrVkBackendContext; the redundant legacy fields were dropped.
Removed: fMinAPIVersion, fInstanceVersion, fExtensions, fFeatures,
fOwnsInstanceAndDevice.
Migration: delete those assignments. Use the typed fVkExtensions in place of
fExtensions, and fDeviceFeatures / fDeviceFeatures2 in place of fFeatures. Skia no
longer manages instance/device ownership through the backend context, so
fOwnsInstanceAndDevice has no replacement.
var ctx = new GRVkBackendContextNative
{
fInstance = instance,
fPhysicalDevice = physicalDevice,
fDevice = device,
fQueue = queue,
fGraphicsQueueIndex = queueIndex,
fMaxAPIVersion = maxVersion,
fVkExtensions = extensions, // instead of fExtensions
fDeviceFeatures = features, // instead of fFeatures
fGetProc = getProc,
// fMinAPIVersion / fInstanceVersion / fExtensions /
// fFeatures / fOwnsInstanceAndDevice — removed, delete these
};
GrVkYcbcrConversionInfo layout change (#3741)A new Components field (and a new GRVkYcbcrComponents struct) was added to the Ycbcr
conversion info, changing its layout. Code that manually constructs the struct or relies on its
layout must account for the new field. (The related GRVkImageInfo.YcbcrConversionInfo retype
from GrVkYcbcrConversionInfo to GRVkYcbcrConversionInfo, and the loss of value-equality on
that type, are captured by the signature diff — the polish step summarizes those from
SkiaSharp.breaking.md.)