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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ The release run heads these entries with the version and opens a fresh

## Unreleased

- **Breaking** (Swift only): `HtmlConfig`'s optional settings are Swift
optionals of the real type rather than `NSNumber`/`NSValue` boxes β€”
`spreadsheetLimit` is a `TableDimensions?`, `initialZoom` a `Double?`, and so
on. ObjC callers see the boxed properties unchanged. Closes #759.

- The HTML renderer warns, rather than silently dropping, when it reaches an
element type it has no `translate_*` for. Across the test corpus that is
`page_break` and nothing else. Towards #150.
Expand Down
5 changes: 5 additions & 0 deletions apple/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ consumer, and a SwiftPM binary target gives the consumer no way to pass
`NS_ERROR_ENUM`. The ObjC API is the API; the Swift target on top is only for
what annotations cannot express, the same way `../android` refuses to
reimplement the java API in kotlin.
- **A boxed `std::optional` is `NS_REFINED_FOR_SWIFT`.** The box moves to
`__name` and `swift/*+Optionals.swift` carries the real optional under the
real name. Swift has no `@encode`, so a boxed struct is otherwise unwritable
from Swift; `@encode` stays in the ObjC layer (`NSValue (ODRTableDimensions)`
in `ODRTable.h`).
- **Pin `os.version` in every conan profile.** An unset deployment target floats
with the runner's SDK and would disagree with `Package.swift`'s `platforms:`;
`CMakeLists.txt` fails the configure rather than let that ship.
Expand Down
8 changes: 8 additions & 0 deletions apple/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ for view in service.views {
Nothing needs configuring first: the renderer's css and JS are part of the
library and are written into the HTML it produces.

`HtmlConfig`'s optional settings are plain Swift optionals of the right type:

```swift
config.spreadsheetLimit = TableDimensions(rows: 100_000, columns: 500)
config.initialZoom = 1.5
config.pageRangeEnd = nil // to the last page
```

## Serve it into a web view

Rendering on demand and serving over loopback is what OpenDocument.ios does, and
Expand Down
18 changes: 12 additions & 6 deletions apple/include/OdrCoreObjC/ODRHtml.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,22 +88,27 @@ NS_SWIFT_NAME(HtmlConfig)
@property(nonatomic) ODRHtmlColorScheme colorScheme;

/// `nil` for no limit.
@property(nonatomic, strong, nullable) NSValue *spreadsheetLimit;
@property(nonatomic, strong, nullable)
NSValue *spreadsheetLimit NS_REFINED_FOR_SWIFT;
/// Most cells written for one sheet; bounds the rows by the sheet's width.
/// `nil` for no budget.
@property(nonatomic, strong, nullable) NSNumber *spreadsheetCellLimit;
@property(nonatomic, strong, nullable)
NSNumber *spreadsheetCellLimit NS_REFINED_FOR_SWIFT;
@property(nonatomic) BOOL spreadsheetLimitByContent;
@property(nonatomic) ODRHtmlTableGridlines spreadsheetGridlines;

@property(nonatomic) ODRHtmlViewportMode viewportMode;
/// Overrides `viewportMode` for spreadsheets when set.
@property(nonatomic, strong, nullable) NSNumber *spreadsheetViewportMode;
@property(nonatomic, strong, nullable)
NSNumber *spreadsheetViewportMode NS_REFINED_FOR_SWIFT;
/// Raw `content` for the viewport meta tag; overrides the modes above.
@property(nonatomic, copy, nullable) NSString *viewportContent;
/// The width the output is shown at, in css pixels; fits paged content to it.
@property(nonatomic, strong, nullable) NSNumber *viewportWidth;
@property(nonatomic, strong, nullable)
NSNumber *viewportWidth NS_REFINED_FOR_SWIFT;
/// The zoom the view opens at, 1 being actual size; `nil` follows the fit.
@property(nonatomic, strong, nullable) NSNumber *initialZoom;
@property(nonatomic, strong, nullable)
NSNumber *initialZoom NS_REFINED_FOR_SWIFT;

/// The least distance the generated content keeps from the view's border. A
/// set side raises the inset the view already has, never lowers it.
Expand All @@ -121,7 +126,8 @@ NS_SWIFT_NAME(HtmlConfig)

/// Render only pages `[begin, end)`, 0-based. `nil` end means to the last page.
@property(nonatomic) uint32_t pageRangeBegin;
@property(nonatomic, strong, nullable) NSNumber *pageRangeEnd;
@property(nonatomic, strong, nullable)
NSNumber *pageRangeEnd NS_REFINED_FOR_SWIFT;

@property(nonatomic) ODRPdfTextMode pdfTextMode;
@property(nonatomic, copy) NSArray<NSString *> *pdfDualLayerFallbackFonts;
Expand Down
9 changes: 9 additions & 0 deletions apple/include/OdrCoreObjC/ODRTable.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ NS_INLINE ODRTableDimensions ODRTableDimensionsMake(uint32_t rows,
return (ODRTableDimensions){.rows = rows, .columns = columns};
}

/// Boxing needs `@encode`, which Swift has no equivalent of.
@interface NSValue (ODRTableDimensions)
+ (NSValue *)odr_valueWithTableDimensions:(ODRTableDimensions)dimensions
NS_SWIFT_NAME(value(tableDimensions:));
/// Zeroes for a value holding anything else.
@property(nonatomic, readonly)
ODRTableDimensions odr_tableDimensionsValue NS_SWIFT_NAME(tableDimensionsValue);
@end

/// A cell address β€” `odr::TablePosition`.
typedef struct ODRTablePosition {
uint32_t column;
Expand Down
19 changes: 19 additions & 0 deletions apple/src/ODRTable.mm
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

#import "ODRInternal.h"

#include <cstring>

#include <odr/table_dimension.hpp>
#include <odr/table_position.hpp>

Expand All @@ -15,6 +17,23 @@
static_assert(sizeof(ODRTablePosition) == sizeof(odr::TablePosition),
"ODRTablePosition drifted from odr::TablePosition");

@implementation NSValue (ODRTableDimensions)

+ (NSValue *)odr_valueWithTableDimensions:(ODRTableDimensions)dimensions {
return [NSValue valueWithBytes:&dimensions
objCType:@encode(ODRTableDimensions)];
}

- (ODRTableDimensions)odr_tableDimensionsValue {
ODRTableDimensions dimensions = ODRTableDimensionsMake(0, 0);
if (strcmp(self.objCType, @encode(ODRTableDimensions)) == 0) {
[self getValue:&dimensions size:sizeof(dimensions)];
}
return dimensions;
}

@end

@implementation ODRTableAddress

// The parses throw on anything that is not a cell address β€” an empty string, a
Expand Down
44 changes: 44 additions & 0 deletions apple/swift/Html+Optionals.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import Foundation

// `HtmlConfig`'s optional settings are `NS_REFINED_FOR_SWIFT`, so the
// `NSNumber`/`NSValue` boxes are `__`-prefixed and these carry the names.

extension HtmlConfig {
public var spreadsheetLimit: TableDimensions? {
get { __spreadsheetLimit?.tableDimensionsValue }
set {
__spreadsheetLimit = newValue.map { NSValue.value(tableDimensions: $0) }
}
}

public var spreadsheetCellLimit: UInt64? {
get { __spreadsheetCellLimit?.uint64Value }
set { __spreadsheetCellLimit = newValue.map(NSNumber.init(value:)) }
}

public var spreadsheetViewportMode: HtmlViewportMode? {
get {
__spreadsheetViewportMode.flatMap {
HtmlViewportMode(rawValue: $0.intValue)
}
}
set {
__spreadsheetViewportMode = newValue.map { NSNumber(value: $0.rawValue) }
}
}

public var viewportWidth: UInt32? {
get { __viewportWidth?.uint32Value }
set { __viewportWidth = newValue.map(NSNumber.init(value:)) }
}

public var initialZoom: Double? {
get { __initialZoom?.doubleValue }
set { __initialZoom = newValue.map(NSNumber.init(value:)) }
}

public var pageRangeEnd: UInt32? {
get { __pageRangeEnd?.uint32Value }
set { __pageRangeEnd = newValue.map(NSNumber.init(value:)) }
}
}
51 changes: 51 additions & 0 deletions apple/tests/OdrCoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,57 @@ final class HtmlTests: XCTestCase {
XCTAssertFalse(try view.writeHtml(resources: &resources).isEmpty)
}

func testSpreadsheetLimitRoundTripsAndReachesTheHtml() throws {
let config = HtmlConfig()
config.spreadsheetLimit = TableDimensions(rows: 2, columns: 1)
XCTAssertEqual(config.spreadsheetLimit?.rows, 2)
XCTAssertEqual(config.spreadsheetLimit?.columns, 1)
config.spreadsheetLimitByContent = false

// A csv renders as a spreadsheet, so this needs no fixture.
let path = try write(
"alpha,beta\ngamma,delta\nepsilon,zeta\n", as: "table.csv")
let file = try DecodedFile.decode(path: path)
let service = try HtmlTranslator.translate(
file: file, cachePath: try temporaryDirectory(), config: config)
var resources: NSArray?
let html = try XCTUnwrap(service.views.first).writeHtml(resources: &resources)

XCTAssertTrue(html.contains("alpha"), "the first cell is missing")
XCTAssertFalse(html.contains("epsilon"), "the row limit did not apply")
XCTAssertFalse(html.contains("beta"), "the column limit did not apply")

config.spreadsheetLimit = nil
XCTAssertNil(config.spreadsheetLimit)
}

func testBoxedNumbersRoundTrip() throws {
let config = HtmlConfig()
config.spreadsheetCellLimit = 1234
config.spreadsheetViewportMode = .fitWidth
config.viewportWidth = 390
config.initialZoom = 1.5
config.pageRangeEnd = 7

XCTAssertEqual(config.spreadsheetCellLimit, 1234)
XCTAssertEqual(config.spreadsheetViewportMode, .fitWidth)
XCTAssertEqual(config.viewportWidth, 390)
XCTAssertEqual(config.initialZoom, 1.5)
XCTAssertEqual(config.pageRangeEnd, 7)

config.spreadsheetCellLimit = nil
config.spreadsheetViewportMode = nil
config.viewportWidth = nil
config.initialZoom = nil
config.pageRangeEnd = nil

XCTAssertNil(config.spreadsheetCellLimit)
XCTAssertNil(config.spreadsheetViewportMode)
XCTAssertNil(config.viewportWidth)
XCTAssertNil(config.initialZoom)
XCTAssertNil(config.pageRangeEnd)
}

func testBringOfflineWritesFiles() throws {
let output = try temporaryDirectory()
let html = try service().bringOffline(to: output)
Expand Down
Loading