Skip to content

Commit

Permalink
Merge pull request #87 from skiptools/svg-images
Browse files Browse the repository at this point in the history
Upgrade to coil3 and add SVG image support
  • Loading branch information
marcprux authored Nov 6, 2024
2 parents 7d007d3 + cc81f58 commit fb6522c
Show file tree
Hide file tree
Showing 31 changed files with 103 additions and 72 deletions.
18 changes: 12 additions & 6 deletions Sources/SkipUI/Skip/skip.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ settings:
contents:
- block: 'create("libs")'
contents:
- 'version("coil-compose", "2.6.0")'
- 'version("androidx-navigation", "2.7.7")'
- 'version("coil", "3.0.0")'
- 'version("androidx-navigation", "2.8.3")'
- 'version("androidx-appcompat", "1.7.0")'
- 'version("androidx-activity", "1.9.0")'
- 'version("androidx-lifecycle-process", "2.8.2")'
- 'version("androidx-material3-adaptive", "1.0.0-beta04")'
- 'version("androidx-activity", "1.9.3")'
- 'version("androidx-lifecycle-process", "2.8.7")'
- 'version("androidx-material3-adaptive", "1.0.0")'

# the version for these libraries is derived from the kotlin-bom declared is skip-model/Sources/SkipModel/Skip/skip.yml
- 'library("androidx-core-ktx", "androidx.core", "core-ktx").withoutVersion()'
Expand All @@ -36,7 +36,10 @@ settings:
- 'library("androidx-lifecycle-process", "androidx.lifecycle", "lifecycle-process").versionRef("androidx-lifecycle-process")'
- 'library("androidx-compose-material3-adaptive", "androidx.compose.material3.adaptive", "adaptive").versionRef("androidx-material3-adaptive")'

- 'library("coil-compose", "io.coil-kt", "coil-compose").versionRef("coil-compose")'
- 'library("coil-compose", "io.coil-kt.coil3", "coil-compose").versionRef("coil")'
- 'library("coil-network-okhttp", "io.coil-kt.coil3", "coil-network-okhttp").versionRef("coil")'
- 'library("coil-svg", "io.coil-kt.coil3", "coil-svg").versionRef("coil")'
#- 'library("coil-gif", "io.coil-kt.coil3", "coil-gif").versionRef("coil")'

- 'library("androidx-compose-ui-test", "androidx.compose.ui", "ui-test").withoutVersion()'
- 'library("androidx-compose-ui-test-junit4", "androidx.compose.ui", "ui-test-junit4").withoutVersion()'
Expand Down Expand Up @@ -73,6 +76,9 @@ build:
- 'api(libs.androidx.activity.compose)'
- 'api(libs.androidx.lifecycle.process)'
- 'implementation(libs.coil.compose)'
- 'implementation(libs.coil.network.okhttp)'
- 'implementation(libs.coil.svg)'
#- 'implementation(libs.coil.gif)'

- 'testImplementation(libs.androidx.compose.ui.test)'
- 'androidTestImplementation(libs.androidx.compose.ui.test)'
Expand Down
2 changes: 1 addition & 1 deletion Sources/SkipUI/SkipUI/Animation/Transition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
#else
#elseif canImport(CoreGraphics)
import struct CoreGraphics.CGFloat
import struct CoreGraphics.CGSize
#endif
Expand Down
91 changes: 53 additions & 38 deletions Sources/SkipUI/SkipUI/Components/AsyncImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,18 @@ import Foundation
#if SKIP
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
import coil.size.Size
import android.webkit.MimeTypeMap
import coil.fetch.Fetcher
import coil.fetch.FetchResult
import coil.fetch.SourceResult
import coil.ImageLoader
import coil.decode.DataSource
import coil.decode.ImageSource
import coil.request.Options
import coil3.compose.SubcomposeAsyncImage
import coil3.request.ImageRequest
import coil3.size.Size
import coil3.fetch.Fetcher
import coil3.fetch.FetchResult
import coil3.ImageLoader
import coil3.decode.DataSource
import coil3.decode.ImageSource
import coil3.PlatformContext
import coil3.asImage
import kotlin.math.roundToInt
import okio.buffer
import okio.source
#endif
Expand Down Expand Up @@ -77,6 +78,8 @@ public struct AsyncImage : View {
let requestSource: Any = JarURLFetcher.isJarURL(url) ? url : urlString
let model = ImageRequest.Builder(LocalContext.current)
.fetcherFactory(JarURLFetcher.Factory())
.decoderFactory(coil3.svg.SvgDecoder.Factory())
//.decoderFactory(coil3.gif.GifDecoder.Factory())
.decoderFactory(PdfDecoder.Factory())
.data(requestSource)
.size(Size.ORIGINAL)
Expand Down Expand Up @@ -134,61 +137,59 @@ public enum AsyncImagePhase {
#if SKIP
/// A Coil fetcher that handles `skip.foundation.URL` instances for the `jar:` scheme.
final class JarURLFetcher : Fetcher {
private let data: URL
private let options: Options
private let url: URL
private let options: coil3.request.Options

static func isJarURL(_ url: URL) -> Bool {
return url.absoluteString.hasPrefix("jar")
}

init(data: URL, options: Options) {
self.data = data
init(url: URL, options: coil3.request.Options) {
self.url = url
self.options = options
}

override func fetch() async -> FetchResult {
return SourceResult(
source: ImageSource(
source: data.kotlin().toURL().openConnection().getInputStream().source().buffer(),
context: options.context
),
mimeType: MimeTypeMap.getSingleton().getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(data.absoluteString)),
dataSource: DataSource.DISK
)
let ctx = options.context
let stream = url.kotlin().toURL().openConnection().getInputStream().source().buffer()
let source = coil3.decode.ImageSource(source: stream, fileSystem: okio.FileSystem.SYSTEM)
let mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(url.absoluteString))
let dataSource: coil3.decode.DataSource = coil3.decode.DataSource.DISK
return coil3.fetch.SourceFetchResult(source: source, mimeType: mimeType, dataSource: dataSource)
}

final class Factory : Fetcher.Factory<URL> {
override func create(data: URL, options: Options, imageLoader: ImageLoader) -> Fetcher? {
override func create(data: URL, options: coil3.request.Options, imageLoader: ImageLoader) -> Fetcher? {
if (!JarURLFetcher.isJarURL(data)) { return nil }
return JarURLFetcher(data: data, options: options)
return JarURLFetcher(url: data, options: options)
}
}
}


class PdfDecoder : coil.decode.Decoder {
let sourceResult: coil.fetch.SourceResult
let options: coil.request.Options
class PdfDecoder : coil3.decode.Decoder {
let sourceResult: coil3.fetch.SourceFetchResult
let options: coil3.request.Options

final class Factory : coil.decode.Decoder.Factory {
override func create(result: coil.fetch.SourceResult, options: coil.request.Options, imageLoader: coil.ImageLoader) -> coil.decode.Decoder? {
logger.log("PdfDecoder.Factory.create result=\(result) options=\(options) imageLoader=\(imageLoader)")
final class Factory : coil3.decode.Decoder.Factory {
override func create(result: coil3.fetch.SourceFetchResult, options: coil3.request.Options, imageLoader: coil3.ImageLoader) -> coil3.decode.Decoder? {
//logger.debug("PdfDecoder.Factory.create result=\(result) options=\(options) imageLoader=\(imageLoader)")
return PdfDecoder(sourceResult: result, options: options)
}
}

init(sourceResult: coil.fetch.SourceResult, options: coil.request.Options) {
init(sourceResult: coil3.fetch.SourceFetchResult, options: coil3.request.Options) {
self.sourceResult = sourceResult
self.options = options
}

override func decode() async -> coil.decode.DecodeResult? {
let src: coil.decode.ImageSource = sourceResult.source
override func decode() async -> coil3.decode.DecodeResult? {
let src: coil3.decode.ImageSource = sourceResult.source
let source: okio.BufferedSource = src.source()

// make sure it is a PDF image by scanning for "%PDF-" (25 50 44 46 2D)
let peek = source.peek()
// logger.log("PdfDecoder.decode peek \(peek.readByte()) \(peek.readByte()) \(peek.readByte()) \(peek.readByte()) \(peek.readByte())")
// logger.debug("PdfDecoder.decode peek \(peek.readByte()) \(peek.readByte()) \(peek.readByte()) \(peek.readByte()) \(peek.readByte())")

if peek.readByte() != Byte(0x25) { return nil } // %
if peek.readByte() != Byte(0x50) { return nil } // P
Expand All @@ -199,7 +200,6 @@ class PdfDecoder : coil.decode.Decoder {
// Unfortunately, PdfRenderer requires a ParcelFileDescriptor, which can only be created from an actual file, and not the JarInputStream from which we load assets from the .apk; so we need to write the PDF out to a temporary file in order to be able to render the PDF to a Bitmap that Coil can use
// Fortunately, even through we are loading from a buffer, Coil's ImageSource.file() function will: “Return a Path that resolves to a file containing this ImageSource's data. If this image source is backed by a BufferedSource, a temporary file containing this ImageSource's data will be created.”
let imageFile = src.file().toFile()
logger.log("PdfDecoder.decode result=\(sourceResult) options=\(options) imageFile=\(imageFile)")

let parcelFileDescriptor = android.os.ParcelFileDescriptor.open(imageFile, android.os.ParcelFileDescriptor.MODE_READ_ONLY)
defer { parcelFileDescriptor.close() }
Expand All @@ -210,13 +210,28 @@ class PdfDecoder : coil.decode.Decoder {
let page = pdfRenderer.openPage(0)
defer { page.close() }

let width = page.width
let height = page.height
let density = options.context.resources.displayMetrics.density

let srcWidth = Double(page.width * density)
let srcHeight = Double(page.height * density)

let optionsWidth = (options.size.width as? coil3.size.Dimension.Pixels)?.px.toDouble()
let optionsHeight = (options.size.height as? coil3.size.Dimension.Pixels)?.px.toDouble()

let dstWidth: Double = optionsWidth ?? srcWidth
let dstHeight: Double = optionsHeight ?? srcHeight

let scale = coil3.decode.DecodeUtils.computeSizeMultiplier(srcWidth: srcWidth, srcHeight: srcHeight, dstWidth: dstWidth, dstHeight: dstHeight, scale: options.scale)

let width = (scale * srcWidth).roundToInt()
let height = (scale * srcHeight).roundToInt()

logger.debug("PdfDecoder.decode result=\(sourceResult) options=\(options) imageFile=\(imageFile) width=\(width) height=\(height)")
let bitmap = android.graphics.Bitmap.createBitmap(width, height, android.graphics.Bitmap.Config.ARGB_8888)
page.render(bitmap, nil, nil, android.graphics.pdf.PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)

let drawable = android.graphics.drawable.BitmapDrawable(options.context.resources, bitmap)
return coil.decode.DecodeResult(drawable: drawable, isSampled: false)
return coil3.decode.DecodeResult(image: drawable.asImage(), isSampled: true)
}
}
#endif
10 changes: 6 additions & 4 deletions Sources/SkipUI/SkipUI/Components/Image.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ import androidx.compose.ui.layout.ScaleFactor
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
#else
import coil3.compose.SubcomposeAsyncImage
import coil3.request.ImageRequest
#elseif canImport(CoreGraphics)
import struct CoreGraphics.CGFloat
import struct CoreGraphics.CGRect
import struct CoreGraphics.CGSize
Expand Down Expand Up @@ -129,9 +129,11 @@ public struct Image : View, Equatable {
let url = asset.url
let model = ImageRequest.Builder(LocalContext.current)
.fetcherFactory(JarURLFetcher.Factory())
.decoderFactory(coil3.svg.SvgDecoder.Factory())
//.decoderFactory(coil3.gif.GifDecoder.Factory())
.decoderFactory(PdfDecoder.Factory())
.data(url)
.size(coil.size.Size.ORIGINAL)
.size(coil3.size.Size.ORIGINAL)
.memoryCacheKey(url.description)
.diskCacheKey(url.description)
.build()
Expand Down
2 changes: 1 addition & 1 deletion Sources/SkipUI/SkipUI/Components/Spacer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
#else
#elseif canImport(CoreGraphics)
import struct CoreGraphics.CGFloat
#endif

Expand Down
2 changes: 1 addition & 1 deletion Sources/SkipUI/SkipUI/Containers/Grid.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#if SKIP
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.ui.unit.dp
#else
#elseif canImport(CoreGraphics)
import struct CoreGraphics.CGFloat
#endif

Expand Down
2 changes: 1 addition & 1 deletion Sources/SkipUI/SkipUI/Containers/HStack.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
#else
#elseif canImport(CoreGraphics)
import struct CoreGraphics.CGFloat
import struct CoreGraphics.CGRect
import struct CoreGraphics.CGSize
Expand Down
2 changes: 1 addition & 1 deletion Sources/SkipUI/SkipUI/Containers/LazyHGrid.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
#else
#elseif canImport(CoreGraphics)
import struct CoreGraphics.CGFloat
#endif

Expand Down
2 changes: 1 addition & 1 deletion Sources/SkipUI/SkipUI/Containers/LazyHStack.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
#else
#elseif canImport(CoreGraphics)
import struct CoreGraphics.CGFloat
#endif

Expand Down
2 changes: 1 addition & 1 deletion Sources/SkipUI/SkipUI/Containers/LazyVGrid.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
#else
#elseif canImport(CoreGraphics)
import struct CoreGraphics.CGFloat
#endif

Expand Down
2 changes: 1 addition & 1 deletion Sources/SkipUI/SkipUI/Containers/LazyVStack.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
#else
#elseif canImport(CoreGraphics)
import struct CoreGraphics.CGFloat
#endif

Expand Down
2 changes: 1 addition & 1 deletion Sources/SkipUI/SkipUI/Containers/List.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ import org.burnoutcrew.reorderable.ReorderableLazyListState
import org.burnoutcrew.reorderable.detectReorderAfterLongPress
import org.burnoutcrew.reorderable.rememberReorderableLazyListState
import org.burnoutcrew.reorderable.reorderable
#else
#elseif canImport(CoreGraphics)
import struct CoreGraphics.CGFloat
#endif

Expand Down
2 changes: 1 addition & 1 deletion Sources/SkipUI/SkipUI/Containers/ScrollView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
#else
#elseif canImport(CoreGraphics)
import struct CoreGraphics.CGFloat
import struct CoreGraphics.CGRect
#endif
Expand Down
2 changes: 1 addition & 1 deletion Sources/SkipUI/SkipUI/Containers/VStack.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
#else
#elseif canImport(CoreGraphics)
import struct CoreGraphics.CGFloat
import struct CoreGraphics.CGRect
import struct CoreGraphics.CGSize
Expand Down
2 changes: 1 addition & 1 deletion Sources/SkipUI/SkipUI/Containers/ZStack.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
#else
#elseif canImport(CoreGraphics)
import struct CoreGraphics.CGRect
import struct CoreGraphics.CGSize
#endif
Expand Down
2 changes: 1 addition & 1 deletion Sources/SkipUI/SkipUI/Controls/Button.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import androidx.compose.material3.RippleConfiguration
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
#else
#elseif canImport(CoreGraphics)
import struct CoreGraphics.CGFloat
import struct CoreGraphics.CGRect
#endif
Expand Down
2 changes: 1 addition & 1 deletion Sources/SkipUI/SkipUI/Graphics/Gradient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import androidx.compose.ui.graphics.ShaderBrush
import androidx.compose.ui.graphics.TileMode
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
#else
#elseif canImport(CoreGraphics)
import struct CoreGraphics.CGFloat
#endif

Expand Down
2 changes: 1 addition & 1 deletion Sources/SkipUI/SkipUI/Graphics/Path.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import androidx.compose.ui.graphics.Matrix
import androidx.compose.ui.graphics.PathOperation
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
#else
#elseif canImport(CoreGraphics)
import struct CoreGraphics.CGAffineTransform
import struct CoreGraphics.CGFloat
import struct CoreGraphics.CGRect
Expand Down
2 changes: 1 addition & 1 deletion Sources/SkipUI/SkipUI/Graphics/Shape.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import androidx.compose.ui.graphics.drawscope.inset
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
#else
#elseif canImport(CoreGraphics)
import struct CoreGraphics.CGFloat
import struct CoreGraphics.CGRect
import struct CoreGraphics.CGSize
Expand Down
2 changes: 1 addition & 1 deletion Sources/SkipUI/SkipUI/Graphics/StrokeStyle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public enum CGLineCap : Int, Sendable {
public enum CGLineJoin : Int, Sendable {
case miter, round, bevel
}
#else
#elseif canImport(CoreGraphics)
import struct CoreGraphics.CGFloat
import enum CoreGraphics.CGLineCap
import enum CoreGraphics.CGLineJoin
Expand Down
2 changes: 1 addition & 1 deletion Sources/SkipUI/SkipUI/Layout/EdgeInsets.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp
#else
#elseif canImport(CoreGraphics)
import struct CoreGraphics.CGFloat
#endif

Expand Down
2 changes: 1 addition & 1 deletion Sources/SkipUI/SkipUI/Layout/GeometryProxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#if SKIP
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.unit.Density
#else
#elseif canImport(CoreGraphics)
import struct CoreGraphics.CGRect
import struct CoreGraphics.CGSize
#endif
Expand Down
Loading

0 comments on commit fb6522c

Please sign in to comment.