AdaptiveGlyphKit renders existing artwork as an inline adaptive image glyph in Apple text. Glyphs flow inside a sentence like Genmoji, scale with Dynamic Type, and can carry an accessibility description.
The images in this README are real NSAdaptiveImageGlyph output rendered
through TextKit 2, generated by
Scripts/generate-readme-images.swift.
NSAdaptiveImageGlyph is the system type behind Genmoji and custom stickers,
but its public initializer accepts only prebuilt image content. AdaptiveGlyphKit
is an experimental compatibility bridge: it forges the minimal accepted content
shape on encoding-capable platforms and safely consumes bounded pre-forged
content on every supported platform.
Add the package with a library-compatible version range:
dependencies: [
.package(
url: "https://github.com/joshlacal/AdaptiveGlyphKit.git",
.upToNextMinor(from: "0.1.0")),
]AdaptiveGlyphKit has no third-party dependencies.
import AdaptiveGlyphKit
import SwiftUI
let renderedGlyph: AttributedString = .adaptiveImageGlyph(
from: pngData,
contentIdentifier: stableID,
accessibilityDescription: "A round blue cat",
fallback: ":blobcat:")
struct GlyphRow: View {
let renderedGlyph: AttributedString
var body: some View {
Text(renderedGlyph)
}
}Image decode, EXIF normalization, resize, HEIC encode, structural parse, and glyph parse are synchronous and must be performed or cached outside SwiftUI body.
Because the glyph is a real adaptive image glyph, it tracks the surrounding font — body text, titles, and large Dynamic Type sizes all render it at the right scale:
| Platform | Forge source images | Consume pre-forged content | Render |
|---|---|---|---|
| iOS 18 / iPadOS 18 | Yes | Yes | Yes |
| macOS 15 | Yes | Yes | Yes |
| Mac Catalyst 18 | Yes | Yes | Yes |
| tvOS 18 | Yes | Yes | Yes |
| visionOS 2 | Yes | Yes | Yes |
| watchOS 11 | No, compile-time unavailable | Yes | Yes |
watchOS can consume and render pre-forged content only. Load persisted content
outside body, then build the attributed value:
let watchText = AttributedString.adaptiveImageGlyph(
imageContent: persistedGlyphData,
fallback: ":blobcat:")Forge on an encoding-capable platform, persist or transfer the resulting bytes, then validate and cache them on watchOS before rendering. Structural parsing, glyph parsing, and attributed-string bridging are synchronous too.
makeGlyph(imageContent:) accepts externally forged adaptive-glyph data and
reconstructs glyphs from cached bytes; neither origin bypasses preflight. The
same policy applies to content produced by AdaptiveGlyphKit:
- Input must be nonempty and at most 1,048,576 bytes.
- The type must equal
NSAdaptiveImageGlyph.contentType. - Content must contain one to eight representations.
- Every representation must have integral width and height from 1 through 1,024 pixels.
- Cumulative pixels must be at most 1,048,576, using checked arithmetic.
- There is no caller-selectable unlimited sentinel.
The 1 MiB limit is AdaptiveGlyphKit's intentional 0.1.0 policy, not a documented Apple limit.
Source-image forging defaults to a 512-pixel longer edge and hard-clamps at
1,024 pixels. The Data path decodes with ImageIO and normalizes EXIF
orientation. The CGImage path performs no EXIF work, never upscales, and uses
CoreGraphics resizing only when the source exceeds the selected bound.
Provide a description when the inline object needs an accessible label:
let labeledGlyph = AttributedString.adaptiveImageGlyph(
from: pngData,
contentIdentifier: stableID,
accessibilityDescription: "A round blue cat",
fallback: ":blobcat:")Omitting accessibilityDescription produces an unlabeled inline object. The
contentIdentifier is a stable cache key; it is not accessibility text.
AdaptiveImageGlyphForge.makeImageContent(imageData:...) throws -> DataAdaptiveImageGlyphForge.makeImageContent(cgImage:...) throws -> DataAdaptiveImageGlyphForge.makeGlyph(imageData:...) -> NSAdaptiveImageGlyph?AdaptiveImageGlyphForge.makeGlyph(cgImage:...) -> NSAdaptiveImageGlyph?AdaptiveImageGlyphForge.makeGlyph(imageContent:) -> NSAdaptiveImageGlyph?AttributedString(adaptiveImageGlyph:) -> AttributedString?AttributedString.adaptiveImageGlyph(imageContent:fallback:) -> AttributedString
To splice a glyph into existing text, replace a range with a glyph run:
var text = AttributedString("hello :blobcat: world")
if let range = text.range(of: ":blobcat:"),
let glyph = AdaptiveImageGlyphForge.makeGlyph(
imageData: pngData,
contentIdentifier: stableID),
let run = AttributedString(adaptiveImageGlyph: glyph) {
text.replaceSubrange(range, with: run)
}AppKit text views render adaptive image glyphs when a TextKit 2 NSTextView
opts in:
textView.importsGraphics = true
textView.textStorage?.setAttributedString(NSAttributedString(text))NSTextInputClient.supportsAdaptiveImageGlyph is the read-only capability
flag; importsGraphics is the switch to set. Use an NSTextView when testing
the AppKit rendering path.
AdaptiveGlyphKit is not a Genmoji generator. It reproduces an undocumented
content shape: a HEIC whose TIFF DocumentName carries the identifier and whose
ImageDescription carries the optional accessibility text. A future OS could
change what NSAdaptiveImageGlyph(imageContent:) accepts.
The package emits one bounded HEIC representation with alpha. It does not
reproduce private sizing or alignment metadata, so baseline or optical alignment
may differ from system Genmoji at extreme sizes. All glyph builders return nil
on rejection, and the high-level attributed-string functions return readable
fallback text. Never assume glyph construction is guaranteed.
MIT. See LICENSE.

