Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Nextcloud.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -5775,7 +5775,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 6;
CURRENT_PROJECT_VERSION = 7;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = NKUJUXUJ3B;
ENABLE_STRICT_OBJC_MSGSEND = YES;
Expand Down Expand Up @@ -5841,7 +5841,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 6;
CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_TEAM = NKUJUXUJ3B;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
Expand Down
21 changes: 10 additions & 11 deletions iOSClient/Menu/NCContextMenuMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -498,19 +498,18 @@ class NCContextMenuMain: NSObject {
if shouldShowMenu {
let deferredElement = UIDeferredMenuElement { completion in
Task {
var iconImage = UIImage()
var iconImage = UIImage(systemName: "exclamationmark.triangle.fill")

if let iconUrl = item.icon {
if let image = await NCUtility().convertSVGtoPNGWriteToUserData(
serverUrl: metadata.urlBase + iconUrl,
rewrite: false,
account: metadata.account
).image {
let image = image.rasterResized(to: CGSize(width: 20, height: 20))
iconImage = image.withTintColor(
NCBrandColor.shared.iconImageColor,
renderingMode: .alwaysOriginal
)
let results = await NextcloudKit.shared.downloadContentAsync(serverUrl: metadata.urlBase + iconUrl, account: metadata.account)
if results.error == .success, let data = results.responseData?.data,
let image = try? await NCSVGRenderer().renderSVGToUIImage(
svgData: data,
size: CGSize(width: UIScreen.main.scale * 20,
height: UIScreen.main.scale * 20),
tintColor: NCBrandColor.shared.iconImageColor,
trimTransparentPixels: false) {
iconImage = image
}
}

Expand Down
130 changes: 75 additions & 55 deletions iOSClient/Utility/NCSVGRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ final class NCSVGRenderer: NSObject, WKNavigationDelegate {
svgData: Data?,
size: CGSize = CGSize(width: 256, height: 256),
backgroundColor: UIColor = .clear,
tintColor: UIColor? = nil,
trimTransparentPixels: Bool = true,
alphaThreshold: UInt8 = 0
) async throws -> UIImage? {
Expand All @@ -54,7 +55,7 @@ final class NCSVGRenderer: NSObject, WKNavigationDelegate {
self.webView = webView

// Inline the SVG into the DOM to avoid <img> rasterization path.
let html = makeHTML(svgData: svgData, canvasPointSize: targetPointSize, backgroundColor: backgroundColor)
let html = makeHTML(svgData: svgData, canvasPointSize: targetPointSize, backgroundColor: backgroundColor, tintColor: tintColor)

try await loadHTMLAsync(webView: webView, html: html)
try await waitForInlineSVGReady(webView: webView)
Expand Down Expand Up @@ -95,67 +96,86 @@ final class NCSVGRenderer: NSObject, WKNavigationDelegate {
return webView
}

private func makeHTML(svgData: Data, canvasPointSize: CGSize, backgroundColor: UIColor) -> String {
private func makeHTML(
svgData: Data,
canvasPointSize: CGSize,
backgroundColor: UIColor,
tintColor: UIColor?
) -> String {

let w = Int(canvasPointSize.width.rounded(.down))
let h = Int(canvasPointSize.height.rounded(.down))

// Base64 payload is decoded in JS and inserted as inline SVG markup.
let base64 = svgData.base64EncodedString()
let cssBackground = (backgroundColor == .clear) ? "transparent" : backgroundColor.toCSSColor()

let cssBackground: String = (backgroundColor == .clear)
? "transparent"
: backgroundColor.toCSSColor()

// Non-optional CSS tint (inherit if nil)
let cssTint: String = tintColor.map { $0.toCSSColor() } ?? "inherit"

// Apply fill only when tintColor is provided
let svgFillRule: String = tintColor != nil
? "#container svg { fill: currentColor; }"
: ""

return """
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=\(w), height=\(h), initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<style>
html, body {
margin: 0;
padding: 0;
width: \(w)px;
height: \(h)px;
overflow: hidden;
background: \(cssBackground);
}
#container {
width: 100%;
height: 100%;
display: block;
background: \(cssBackground);
}
/* Make sure the inline SVG scales to fill the canvas. */
#container svg {
width: 100%;
height: 100%;
display: block;
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=\(w), height=\(h), initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<style>
html, body {
margin: 0;
padding: 0;
width: \(w)px;
height: \(h)px;
overflow: hidden;
background: \(cssBackground);
color: \(cssTint);
}

#container {
width: 100%;
height: 100%;
display: block;
background: \(cssBackground);
}

#container svg {
width: 100%;
height: 100%;
display: block;
}

\(svgFillRule)
</style>
</head>
<body>
<div id="container"></div>
<script>
(function() {
const container = document.getElementById('container');
const svgText = atob('\(base64)');
container.innerHTML = svgText;

const svg = container.querySelector('svg');
if (svg) {
const hasViewBox = svg.getAttribute('viewBox');
if (!hasViewBox) {
const width = svg.getAttribute('width') || \(w);
const height = svg.getAttribute('height') || \(h);
svg.setAttribute('viewBox', '0 0 ' + width + ' ' + height);
}
</style>
</head>
<body>
<div id="container"></div>
<script>
(function() {
const container = document.getElementById('container');
const svgText = atob('\(base64)');
container.innerHTML = svgText;

// Ensure a viewBox exists (better scaling behavior for many icons).
const svg = container.querySelector('svg');
if (svg) {
const hasViewBox = svg.getAttribute('viewBox');
if (!hasViewBox) {
const width = svg.getAttribute('width') || \(w);
const height = svg.getAttribute('height') || \(h);
svg.setAttribute('viewBox', '0 0 ' + width + ' ' + height);
}
svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
}
})();
</script>
</body>
</html>
"""
svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
}
})();
</script>
</body>
</html>
"""
}

private func loadHTMLAsync(webView: WKWebView, html: String) async throws {
Expand Down
Loading