From 011f00a2cc5aca611bad76c6f4f4bb1606fe5c41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20C=C3=A9sar=20Fern=C3=A1ndez?= Date: Sun, 10 May 2026 18:17:33 +0200 Subject: [PATCH 1/4] fix(visionOS): include visionOS in iOS guard for LaTeX_Previews+Fonts The other Apple-platform guards across the package already use `#if os(iOS) || os(visionOS)` so visionOS picks up `UIKit` instead of falling through to `Cocoa`. This preview file was missed when visionOS support was added, so building any visionOS target that links LaTeXSwiftUI fails with `Unable to resolve module dependency: 'Cocoa'`. --- Sources/LaTeXSwiftUI/Previews/LaTeX_Previews+Fonts.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/LaTeXSwiftUI/Previews/LaTeX_Previews+Fonts.swift b/Sources/LaTeXSwiftUI/Previews/LaTeX_Previews+Fonts.swift index 9804604..44a361a 100644 --- a/Sources/LaTeXSwiftUI/Previews/LaTeX_Previews+Fonts.swift +++ b/Sources/LaTeXSwiftUI/Previews/LaTeX_Previews+Fonts.swift @@ -25,7 +25,7 @@ import SwiftUI -#if os(iOS) +#if os(iOS) || os(visionOS) import UIKit typealias PlatformFont = UIFont #else From b1a3ee9f328f2d86608c28503c2b50f5e15b58a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20C=C3=A9sar=20Fern=C3=A1ndez?= Date: Sun, 10 May 2026 18:23:03 +0200 Subject: [PATCH 2/4] fix(visionOS): bump minimum visionOS deployment to 2.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `EquationMarker` and the `customAttribute(_:)` call site in `Component.swift` require `TextAttribute`, which is iOS 18 / macOS 15 / visionOS 2.0+. Their `@available(iOS 18.0, macOS 15.0, *)` annotations let `*` expand to the package's visionOS deployment target, which was `.v1` (1.0) — so the package fails to build for visionOS with: 'EquationMarker' is only available in visionOS 2.0 or newer visionOS 1 has been deprecated for over a year and the rest of the package's visionOS guards already assume modern visionOS. Bumping the minimum to 2.0 matches the actual API surface used. --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 782dcf6..d9a6007 100644 --- a/Package.swift +++ b/Package.swift @@ -8,7 +8,7 @@ let package = Package( platforms: [ .iOS(.v15), .macOS(.v12), - .visionOS(.v1) + .visionOS(.v2) ], products: [ .library( From d03ddc97fb28112737af533a8b6c2438f779ce6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20C=C3=A9sar=20Fern=C3=A1ndez?= Date: Wed, 22 Jul 2026 20:43:00 +0200 Subject: [PATCH 3/4] fix: restaura el regex `digits` que MathJaxSwift 3.5.0 trae sin escapar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MathJaxSwift 3.5.0 define `defaultDigits` perdiendo los escapes del regex original de MathJax (`\{,\}` → `{,}`, `\.` → `.`). El `.` sin escapar casa cualquier carácter, así que el tokenizador TeX se come el carácter que sigue a un número: `x^{2}` pierde la llave de cierre y MathJax devuelve "Extra open brace or missing close brace". Con `errorMode: .rendered` eso produce el SVG de error de MathJax (52.9ex × 2.1ex), que rasterizado sale como una caja opaca — en Markitown, una barra negra en lugar de la fórmula. Fijamos el regex correcto en el init de conveniencia, que es el que usan tanto la vista `LaTeX` como `renderToImages`. Ref: colinc86/MathJaxSwift#45 Co-Authored-By: Claude Opus 4.8 --- .../TeXInputProcessorOptions+Extensions.swift | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Sources/LaTeXSwiftUI/Extensions/TeXInputProcessorOptions+Extensions.swift b/Sources/LaTeXSwiftUI/Extensions/TeXInputProcessorOptions+Extensions.swift index 4a12d8d..f7b4bc0 100644 --- a/Sources/LaTeXSwiftUI/Extensions/TeXInputProcessorOptions+Extensions.swift +++ b/Sources/LaTeXSwiftUI/Extensions/TeXInputProcessorOptions+Extensions.swift @@ -37,7 +37,16 @@ extension TeXInputProcessorOptions { convenience init(processEscapes: Bool, errorMode: LaTeX.ErrorMode) { self.init() self.processEscapes = processEscapes - + + // MathJaxSwift 3.5.0 ships `defaultDigits` with its escapes stripped + // (`\{,\}` → `{,}`, `\.` → `.`), so the unescaped `.` matches *any* + // character and the tokenizer swallows whatever follows a number: `x^{2}` + // loses its closing brace and MathJax fails with "Extra open brace or + // missing close brace". Restore MathJax's own default regex. + // See colinc86/MathJaxSwift#45. + digits = #"^(?:[0-9]+(?:\{,\}[0-9]{3})*(?:\.[0-9]*)?|\.[0-9]+)"# + + var packages = TeXInputProcessorOptions.Packages.all if errorMode != .rendered { if let noErrorsIndex = packages.firstIndex(of: TeXInputProcessorOptions.Packages.noerrors) { From 9c0c171471700d29c18bc971320181884a37cf18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20C=C3=A9sar=20Fern=C3=A1ndez?= Date: Fri, 24 Jul 2026 13:30:14 +0200 Subject: [PATCH 4/4] fix(macOS): remove double displayScale division in Image(image:) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renderer.rasterizeSVG already returns an NSImage whose size is the logical (point) size — NSImage(cgImage:size:) is handed the pre-scale size while the bitmap holds size * scale pixels. Image(image:scale:) then divided that point size by displayScale again on macOS, so every formula laid out at half its intended size on Retina (scale 2.0) screens. The cached path (Image(image:)) never divided, so the two paths disagreed. Drop the division and the now-dead scale parameter and NSImage.resized(to:) helper. UIImage already carries its scale, so iOS/visionOS behavior is unchanged. Co-Authored-By: Claude Fable 5 --- .../Extensions/Image+Extensions.swift | 22 +++------ .../Extensions/NSImage+Extensions.swift | 47 ------------------- Sources/LaTeXSwiftUI/Models/Renderer.swift | 2 +- 3 files changed, 7 insertions(+), 64 deletions(-) delete mode 100644 Sources/LaTeXSwiftUI/Extensions/NSImage+Extensions.swift diff --git a/Sources/LaTeXSwiftUI/Extensions/Image+Extensions.swift b/Sources/LaTeXSwiftUI/Extensions/Image+Extensions.swift index ca1b486..a398920 100644 --- a/Sources/LaTeXSwiftUI/Extensions/Image+Extensions.swift +++ b/Sources/LaTeXSwiftUI/Extensions/Image+Extensions.swift @@ -26,25 +26,15 @@ import SwiftUI internal extension Image { - init(image: _Image, scale: CGFloat = 1.0) { + init(image: _Image) { #if os(iOS) || os(visionOS) + // `UIImage(cgImage:scale:orientation:)` already carries the display + // scale, so its `size` is in points. self.init(uiImage: image) #else - if scale > 1.0 { - let scaledSize = NSSize( - width: image.size.width / scale, - height: image.size.height / scale - ) - - let scaledImage = image.resized(to: scaledSize) - if let representation = image.representations.first { - scaledImage.addRepresentation(representation) - } - - self.init(nsImage: scaledImage) - return - } - + // `NSImage(cgImage:size:)` is created with the logical size, so its + // `size` is already in points — dividing by the display scale again + // would lay the image out at half its intended size on Retina screens. self.init(nsImage: image) #endif } diff --git a/Sources/LaTeXSwiftUI/Extensions/NSImage+Extensions.swift b/Sources/LaTeXSwiftUI/Extensions/NSImage+Extensions.swift deleted file mode 100644 index 10b5f93..0000000 --- a/Sources/LaTeXSwiftUI/Extensions/NSImage+Extensions.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// NSImage+Extensions.swift -// LaTeXSwiftUI -// -// Copyright (c) 2023 Colin Campbell -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -// - -#if os(macOS) -import Cocoa - -internal extension NSImage { - - /// Resizes the image. - /// - Parameter newSize: The image's new size. - /// - Returns: A resized image. - func resized(to newSize: CGSize) -> NSImage { - let newImage = NSImage(size: newSize) - newImage.lockFocus() - defer { newImage.unlockFocus() } - self.draw( - in: CGRect(origin: .zero, size: newSize), - from: CGRect(origin: .zero, size: self.size), - operation: .copy, - fraction: 1.0) - return newImage - } - -} -#endif diff --git a/Sources/LaTeXSwiftUI/Models/Renderer.swift b/Sources/LaTeXSwiftUI/Models/Renderer.swift index 20d736e..ec449e4 100644 --- a/Sources/LaTeXSwiftUI/Models/Renderer.swift +++ b/Sources/LaTeXSwiftUI/Models/Renderer.swift @@ -515,7 +515,7 @@ extension Renderer { Cache.shared.setImageCacheValue(image, for: cacheKey) // Finish up - return Image(image: image, scale: displayScale) + return Image(image: image) .renderingMode(renderingMode) .antialiased(true) .interpolation(.high)