You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@corinthia.apache.org by pm...@apache.org on 2015/08/19 17:38:18 UTC

[1/4] incubator-corinthia git commit: Add Objective C framework code from UX Write

Repository: incubator-corinthia
Updated Branches:
  refs/heads/master 5202fbf16 -> 9f851a342


http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDSaveOperation.h
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDSaveOperation.h b/experiments/objcFramework/EDSaveOperation.h
new file mode 100644
index 0000000..944aedc
--- /dev/null
+++ b/experiments/objcFramework/EDSaveOperation.h
@@ -0,0 +1,44 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import <FileClient/FCOperation.h>
+
+@class EDEditor;
+
+typedef void (^EDSaveCompletion)(BOOL cancelled, NSError *error);
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                         EDSaveOperation                                        //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface EDSaveOperation : FCOperation
+
+@property (assign, readonly) NSUInteger completionCount;
+
+- (EDSaveOperation *)initWithEditor:(EDEditor *)editor path:(NSString *)path;
+- (void)addCompletion:(EDSaveCompletion)completion;
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDSaveOperation.m
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDSaveOperation.m b/experiments/objcFramework/EDSaveOperation.m
new file mode 100644
index 0000000..b5f65ba
--- /dev/null
+++ b/experiments/objcFramework/EDSaveOperation.m
@@ -0,0 +1,166 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import "EDSaveOperation.h"
+#import "EDEditor.h"
+#import "EDJSInterface.h"
+#import "EDTiming.h"
+#import "EDFileFormat.h"
+#import <FileClient/FCError.h>
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                         EDSaveOperation                                        //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation EDSaveOperation
+{
+    EDEditor *_editor;
+    NSString *_path;
+    NSMutableArray *_completions;
+}
+
+- (EDSaveOperation *)initWithEditor:(EDEditor *)editor path:(NSString *)path
+{
+    if (!(self = [super init]))
+        return nil;
+    _editor = editor;
+    _path = [path copy];
+    _completions = [NSMutableArray arrayWithCapacity: 0];
+    return self;
+}
+
+- (NSUInteger)completionCount
+{
+    return _completions.count;
+}
+
+- (void)addCompletion:(EDSaveCompletion)completion;
+{
+    [_completions addObject: [completion copy]];
+}
+
+- (void)start
+{
+    [super start];
+
+    assert(self == _editor.activeSave);
+    _editor.delegate.editorIsSaving = YES;
+    // Start the actual save in a separate iteration of the event loop, so that the "Saving" label
+    // and animated activity indicator become visible
+    [self performSelector: @selector(stage1) withObject: nil afterDelay: 0];
+}
+
+- (void)stage1
+{
+    if (!_editor.js.documentModified) {
+        [self succeed: nil];
+        return;
+    }
+
+    NSFileManager *fm = [NSFileManager defaultManager];
+    NSString *tempPath = [NSString stringWithFormat: @"%@.saving", _path];
+    [_editor.saveTiming start];
+
+    if (![_editor.js.main prepareForSave]) {
+        [self fail: [NSError error: @"JS prepareForSave failed"]];
+        return;
+    }
+    [_editor.saveTiming addEntry: @"JS prepare for save"];
+    NSString *html = [_editor.js.main getHTML];
+
+    // Save the document to a temporary file (so if it fails, we still have the original)
+    [_editor.fileFormat save: tempPath html: html completion:^(NSError *error) {
+        if (error != nil) {
+            [self fail: error];
+            return;
+        }
+
+        // Delete temp dir
+        error = nil;
+        if ([fm fileExistsAtPath: _editor.tempDir] && ![fm removeItemAtPath: _editor.tempDir error: &error]) {
+            [self fail: error];
+            return;
+        }
+
+        // Check if the file has actually changed - if not, avoid trigering an upload and updating the
+        // modification time
+        if ([fm contentsEqualAtPath: tempPath andPath: _path]) {
+            [fm removeItemAtPath: tempPath error: nil];
+            [self succeed: nil];
+            return;
+        }
+
+        // Remove old version of file
+        if ([fm fileExistsAtPath: _path] && ![fm removeItemAtPath: _path error: &error]) {
+            [self fail: error];
+            return;
+        }
+
+        // Rename temporary file
+        if (![fm moveItemAtPath: tempPath toPath: _path error: &error]) {
+            [self fail: error];
+            return;
+        }
+
+        // Save succsesful. Tell the sync manager to start uploading the file (if it's on a remote
+        // server), and to do so even if the app is in the background
+        _editor.js.documentModified = NO;
+        [_editor.delegate editorDidSaveFile];
+        [_editor.saveTiming addEntry: @"Completed save"];
+        [self succeed: nil];
+    }];
+}
+
+- (void)cleanup
+{
+    for (EDSaveCompletion completion in _completions) {
+        if (self.status == FCOperationStatusCancelled)
+            completion(YES,nil);
+        else if (self.status == FCOperationStatusFailed)
+            completion(NO,self.privateError);
+        else
+            completion(NO,nil);
+    }
+    _completions = nil; // break retain cycles
+
+    _editor.delegate.editorIsSaving = NO;
+
+    assert(self == _editor.activeSave);
+
+    if (_editor.pendingSave != nil) {
+        _editor.activeSave = _editor.pendingSave;
+        _editor.pendingSave = nil;
+        [_editor.activeSave start];
+    }
+    else {
+        _editor.activeSave = nil;
+    }
+
+    assert(self != _editor.activeSave);
+    [_editor debugSaveStatus];
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDScan.h
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDScan.h b/experiments/objcFramework/EDScan.h
new file mode 100644
index 0000000..0ef58d3
--- /dev/null
+++ b/experiments/objcFramework/EDScan.h
@@ -0,0 +1,64 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import <FileClient/FCOperation.h>
+
+@class EDEditor;
+@class EDScanResults;
+@class EDScanResultsSection;
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                         EDScanParagraph                                        //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface EDScanParagraph : NSObject
+
+@property (copy, readonly) NSString *text;
+@property (copy, readonly) NSString *sectionId;
+
+- (EDScanParagraph *)initWithText:(NSString *)text sectionId:(NSString *)sectionId;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                         EDScanOperation                                        //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface EDScanOperation : FCOperation
+
+@property (weak, readonly) EDEditor *editor;
+@property (strong, readonly) EDScanResults *results;
+@property (strong) EDScanResultsSection *currentSection;
+@property (strong, readonly) NSString *lastSectionId;
+
+- (EDScanOperation *)initWithEditor:(EDEditor *)editor;
+- (void)includeCurrentSection;
+- (void)processParagraph:(EDScanParagraph *)paragraph;
+- (void)foundMatches:(NSArray *)newMatches;
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDScan.m
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDScan.m b/experiments/objcFramework/EDScan.m
new file mode 100644
index 0000000..9353f7c
--- /dev/null
+++ b/experiments/objcFramework/EDScan.m
@@ -0,0 +1,138 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import "EDScan.h"
+#import "EDScanResults.h"
+#import "EDEditor.h"
+#import "EDJSInterface.h"
+#import "EDOutline.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                         EDScanParagraph                                        //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation EDScanParagraph
+
+- (EDScanParagraph *)initWithText:(NSString *)text sectionId:(NSString *)sectionId
+{
+    if (!(self = [super init]))
+        return nil;
+    _text = [text copy];
+    _sectionId = [sectionId copy];
+    return self;
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                         EDScanOperation                                        //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation EDScanOperation
+{
+    BOOL _cancelled;
+}
+
+- (EDScanOperation *)initWithEditor:(EDEditor *)editor
+{
+    if (!(self = [super init]))
+        return nil;
+    _editor = editor;
+    _results = [[EDScanResults alloc] init];
+    return self;
+}
+
+- (void)start
+{
+    [super start];
+
+    [_editor.js.scan reset];
+    [self performSelector: @selector(next) withObject: nil afterDelay: 0.1];
+}
+
+- (void)cancel
+{
+    _cancelled = YES;
+    [super cancel];
+}
+
+- (void)next
+{
+    if (self.status != FCOperationStatusActive)
+        return;
+
+    EDScanParagraph *paragraph = [_editor.js.scan next];
+    if (paragraph == nil) {
+        [self succeed: nil];
+        return;
+    }
+    else {
+        if (paragraph.sectionId != nil) {
+            _lastSectionId = paragraph.sectionId;
+            _currentSection = nil;
+        }
+
+        [self processParagraph: paragraph];
+        [self performSelector: @selector(next) withObject: nil afterDelay: 0];
+    }
+}
+
+- (void)includeCurrentSection
+{
+    if (_currentSection == nil) {
+        EDOutlineItem *item = nil;
+        if (_lastSectionId != nil)
+            item = [_editor.outline lookupItem: _lastSectionId];
+        if (item != nil)
+            _currentSection = [[EDScanResultsSection alloc] initWithTitle: item.title];
+        else
+            _currentSection = [[EDScanResultsSection alloc] initWithTitle: nil];
+        [_results.sections addObject: _currentSection];
+    }
+}
+
+- (void)processParagraph:(EDScanParagraph *)paragraph
+{
+    // Default implementation does nothing
+}
+
+- (void)foundMatches:(NSArray *)newMatches
+{
+    for (EDScanMatch *match in newMatches)
+        [_editor.js.scan showMatch: match.matchId];
+
+    // We must add these *after* calling the JS showMatch method, as modifying the results will cause
+    // ScanMode to go to the first one if there were no previous matches, and this will modify the document
+    // structure, by adding a selection highlight around the match.
+    for (EDScanMatch *match in newMatches) {
+        [self.currentSection.matches addObject: match];
+        [self.results addMatch: match];
+    }
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDScanResults.h
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDScanResults.h b/experiments/objcFramework/EDScanResults.h
new file mode 100644
index 0000000..d9857b4
--- /dev/null
+++ b/experiments/objcFramework/EDScanResults.h
@@ -0,0 +1,73 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import <Foundation/Foundation.h>
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                           EDScanMatch                                          //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface EDScanMatch : NSObject
+
+@property (assign, readonly) int matchId;
+@property (copy, readonly) NSString *text;
+
+- (EDScanMatch *)initWithMatchId:(int)matchId text:(NSString *)text;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                      EDScanResultsSection                                      //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface EDScanResultsSection : NSObject
+
+@property (copy, readonly) NSString *title;
+@property (strong, readonly) NSMutableArray *matches;
+
+- (EDScanResultsSection *)initWithTitle:(NSString *)title;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                          EDScanResults                                         //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface EDScanResults : NSObject
+
+@property (readonly) NSMutableArray *sections;
+
+- (EDScanResults *)init;
+- (NSUInteger)matchCount;
+- (EDScanMatch *)matchAtIndex:(NSUInteger)index;
+- (void)addMatch:(EDScanMatch *)match;
+- (void)removeMatch:(EDScanMatch *)match;
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDScanResults.m
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDScanResults.m b/experiments/objcFramework/EDScanResults.m
new file mode 100644
index 0000000..e5ed9f2
--- /dev/null
+++ b/experiments/objcFramework/EDScanResults.m
@@ -0,0 +1,109 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import "EDScanResults.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                           EDScanMatch                                          //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation EDScanMatch
+
+- (EDScanMatch *)initWithMatchId:(int)matchId text:(NSString *)text
+{
+    if (!(self = [super init]))
+        return nil;
+    _matchId = matchId;
+    _text = [text copy];
+    return self;
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                      EDScanResultsSection                                      //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation EDScanResultsSection
+
+- (EDScanResultsSection *)initWithTitle:(NSString *)title
+{
+    if (!(self = [super init]))
+        return nil;
+    _title = [title copy];
+    _matches = [NSMutableArray arrayWithCapacity: 0];
+    return self;
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                          EDScanResults                                         //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation EDScanResults
+{
+    NSMutableArray *_allMatches;
+}
+
+- (EDScanResults *)init
+{
+    if (!(self = [super init]))
+        return nil;
+    _sections = [NSMutableArray arrayWithCapacity: 0];
+    _allMatches = [NSMutableArray arrayWithCapacity: 0];
+    return self;
+}
+
+- (NSUInteger)matchCount
+{
+    return _allMatches.count;
+}
+
+- (EDScanMatch *)matchAtIndex:(NSUInteger)index
+{
+    return [_allMatches objectAtIndex: index];
+}
+
+- (void)addMatch:(EDScanMatch *)match
+{
+    [self willChangeValueForKey: @"matchCount"];
+    [_allMatches addObject: match];
+    [self didChangeValueForKey: @"matchCount"];
+}
+
+- (void)removeMatch:(EDScanMatch *)match
+{
+    [self willChangeValueForKey: @"matchCount"];
+    [_allMatches removeObject: match];
+    [self didChangeValueForKey: @"matchCount"];
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDSelectionFormatting.h
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDSelectionFormatting.h b/experiments/objcFramework/EDSelectionFormatting.h
new file mode 100644
index 0000000..27dc82d
--- /dev/null
+++ b/experiments/objcFramework/EDSelectionFormatting.h
@@ -0,0 +1,55 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import <Foundation/Foundation.h>
+#import <Editor/DocFormats.h>
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                      EDSelectionFormatting                                     //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface EDSelectionFormatting : NSObject
+
+@property (assign, readonly) CSSProperties *cssProperties;
+
+@property (assign, readonly) BOOL shift;
+@property (assign, readonly) BOOL inBrackets;
+@property (assign, readonly) BOOL inQuotes;
+@property (assign, readonly) BOOL inImage;
+@property (assign, readonly) BOOL inFigure;
+@property (assign, readonly) BOOL inTable;
+@property (assign, readonly) BOOL inReference;
+@property (assign, readonly) BOOL inLink;
+@property (assign, readonly) BOOL inTOC;
+@property (assign, readonly) BOOL inUL;
+@property (assign, readonly) BOOL inOL;
+@property (assign, readonly) BOOL inTT;
+@property (assign, readonly) NSString *inItemTitle;
+
+- (EDSelectionFormatting *)init;
+- (EDSelectionFormatting *)initWithProperties:(NSDictionary *)properties;
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDSelectionFormatting.m
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDSelectionFormatting.m b/experiments/objcFramework/EDSelectionFormatting.m
new file mode 100644
index 0000000..1bffa5f
--- /dev/null
+++ b/experiments/objcFramework/EDSelectionFormatting.m
@@ -0,0 +1,73 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import "EDSelectionFormatting.h"
+#import "EDUtil.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                      EDSelectionFormatting                                     //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation EDSelectionFormatting
+
+- (EDSelectionFormatting *)init
+{
+    return [self initWithProperties: @{ }];
+}
+
+- (EDSelectionFormatting *)initWithProperties:(NSDictionary *)properties
+{
+    if (!(self = [super init]))
+        return nil;
+    _cssProperties = CSSPropertiesNew();
+
+    DFHashTable *hashTable = HashTableFromNSDictionary(properties);
+    CSSPropertiesUpdateFromRaw(_cssProperties,hashTable);
+    DFHashTableRelease(hashTable);
+
+    return self;
+}
+
+- (void)dealloc
+{
+    CSSPropertiesRelease(_cssProperties);
+}
+
+- (BOOL)shift { return DFStringEquals("true",CSSGet(_cssProperties,"-uxwrite-shift")); }
+- (BOOL)inBrackets { return DFStringEquals("true",CSSGet(_cssProperties,"-uxwrite-in-brackets")); }
+- (BOOL)inQuotes { return DFStringEquals("true",CSSGet(_cssProperties,"-uxwrite-in-quotes")); }
+- (BOOL)inImage { return DFStringEquals("true",CSSGet(_cssProperties,"-uxwrite-in-image")); }
+- (BOOL)inFigure { return DFStringEquals("true",CSSGet(_cssProperties,"-uxwrite-in-figure")); }
+- (BOOL)inTable { return DFStringEquals("true",CSSGet(_cssProperties,"-uxwrite-in-table")); }
+- (BOOL)inReference { return DFStringEquals("true",CSSGet(_cssProperties,"-uxwrite-in-reference")); }
+- (BOOL)inLink { return DFStringEquals("true",CSSGet(_cssProperties,"-uxwrite-in-link")); }
+- (BOOL)inTOC { return DFStringEquals("true",CSSGet(_cssProperties,"-uxwrite-in-toc")); }
+- (BOOL)inUL { return DFStringEquals("true",CSSGet(_cssProperties,"-uxwrite-in-ul")); }
+- (BOOL)inOL { return DFStringEquals("true",CSSGet(_cssProperties,"-uxwrite-in-ol")); }
+- (BOOL)inTT { return DFStringEquals("true",CSSGet(_cssProperties,"-uxwrite-in-tt")); }
+- (NSString *)inItemTitle { return NSStringFromC(CSSGet(_cssProperties,"-uxwrite-in-item-title")); }
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDStyle.h
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDStyle.h b/experiments/objcFramework/EDStyle.h
new file mode 100644
index 0000000..f71e096
--- /dev/null
+++ b/experiments/objcFramework/EDStyle.h
@@ -0,0 +1,59 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import <Foundation/Foundation.h>
+#import <Editor/DocFormats.h>
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                             EDStyle                                            //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface EDStyle : NSObject
+
+@property (readonly) CSSStyle *cssStyle;
+
++ (EDStyle *)styleWithCSSStyle:(CSSStyle *)cssStyle;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                            EDCascade                                           //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface EDCascade : NSObject
+
+@property (assign, readwrite, nonatomic) CSSProperties *defaultProperties;
+@property (assign, readwrite, nonatomic) CSSProperties *docProperties;
+@property (assign, readwrite, nonatomic) CSSProperties *styleProperties;
+@property (assign, readwrite, nonatomic) CSSProperties *directProperties;
+
+- (EDCascade *)init;
+- (NSString *)get:(NSString *)name;
+- (void)set:(NSString *)name value:(NSString *)value;
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDStyle.m
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDStyle.m b/experiments/objcFramework/EDStyle.m
new file mode 100644
index 0000000..a45d7a9
--- /dev/null
+++ b/experiments/objcFramework/EDStyle.m
@@ -0,0 +1,150 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import "EDStyle.h"
+#import "EDUtil.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                             EDStyle                                            //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation EDStyle
+
+- (void)dealloc
+{
+    CSSStyleRelease(_cssStyle);
+}
+
+- (BOOL)isEqual:(id)object
+{
+    if ((object == nil) || ![object isKindOfClass: [EDStyle class]])
+        return NO;
+    EDStyle *otherStyle = (EDStyle *)object;
+    return (_cssStyle == otherStyle.cssStyle);
+}
+
++ (EDStyle *)styleWithCSSStyle:(CSSStyle *)cssStyle
+{
+    if (cssStyle == NULL)
+        return nil;
+    EDStyle *style = [[EDStyle alloc] init];
+    style->_cssStyle = CSSStyleRetain(cssStyle);
+    return style;
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                            EDCascade                                           //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation EDCascade
+
+- (EDCascade *)init
+{
+    if (!(self = [super init]))
+        return nil;
+    return self;
+}
+
+- (void)dealloc
+{
+    CSSPropertiesRelease(_defaultProperties);
+    CSSPropertiesRelease(_docProperties);
+    CSSPropertiesRelease(_styleProperties);
+    CSSPropertiesRelease(_directProperties);
+}
+
+- (void)setPropertiesPtr:(CSSProperties **)ptr value:(CSSProperties *)value
+{
+    if (*ptr != value) {
+        CSSPropertiesRelease(*ptr);
+        *ptr = CSSPropertiesRetain(value);
+    }
+}
+
+- (void)setDefaultProperties:(CSSProperties *)defaultProperties
+{
+    [self setPropertiesPtr: &_defaultProperties value: defaultProperties];
+}
+
+- (void)setDocProperties:(CSSProperties *)docProperties
+{
+    [self setPropertiesPtr: &_docProperties value: docProperties];
+}
+
+- (void)setStyleProperties:(CSSProperties *)styleProperties
+{
+    [self setPropertiesPtr: &_styleProperties value: styleProperties];
+}
+
+- (void)setDirectProperties:(CSSProperties *)directProperties
+{
+    [self setPropertiesPtr: &_directProperties value: directProperties];
+}
+
+- (NSString *)get:(NSString *)name includeDirect:(BOOL)includeDirect
+{
+    CSSProperties *cascade[4] = { _directProperties, _styleProperties, _docProperties, _defaultProperties };
+    size_t count = 4;
+    size_t first = includeDirect ? 0 : 1;
+    for (size_t i = first; i < count; i++) {
+        if (cascade[i] != NULL) {
+            const char *value = CSSGet(cascade[i],name.UTF8String);
+            if (value != NULL)
+                return [NSString stringWithUTF8String: value];
+        }
+    }
+    return nil;
+}
+
+- (NSString *)get:(NSString *)name
+{
+    return [self get: name includeDirect: YES];
+}
+
+- (void)set:(NSString *)name value:(NSString *)value
+{
+    if (_directProperties == NULL)
+        return;
+
+    // If the value is unchanged, don't call CSSPut; this avoids triggering an unnecessary change notification
+    NSString *oldValue = [self get: name includeDirect: YES];
+    if (EDStringEquals(oldValue,value))
+        return;
+
+    // If the value being set is the same as the value inherited from the style, document, or detauls, remove
+    // the direct formatting property. Otherwise, set it to override the inherited value.
+    NSString *parentValue = [self get: name includeDirect: NO];
+    if (EDStringEquals(parentValue,value))
+        CSSPut(_directProperties,name.UTF8String,NULL);
+    else
+        CSSPut(_directProperties,name.UTF8String,value.UTF8String);
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDTiming.h
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDTiming.h b/experiments/objcFramework/EDTiming.h
new file mode 100644
index 0000000..0dba3a9
--- /dev/null
+++ b/experiments/objcFramework/EDTiming.h
@@ -0,0 +1,80 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import <Foundation/Foundation.h>
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                          EDTimingEntry                                         //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface EDTimingEntry : NSObject
+
+@property (copy, readonly) NSString *name;
+@property (assign, readonly) NSTimeInterval time;
+@property (assign, readonly) NSTimeInterval min;
+@property (assign, readonly) NSTimeInterval max;
+
+- (EDTimingEntry *)initWithName:(NSString *)name time:(NSTimeInterval)time;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                          EDTimingInfo                                          //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface EDTimingInfo : NSObject
+
+@property (strong, readonly) NSArray *entries;
+@property (assign, readonly) NSTimeInterval total;
+
+- (EDTimingInfo *)init;
+- (void)start;
+- (void)addEntry:(NSString *)name;
+- (NSString *)descriptionWithTitle:(NSString *)title;
+- (NSString *)detailedDescription;
+
++ (EDTimingInfo *)computeAverages:(NSArray *)timings;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                         EDTimingRecords                                        //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface EDTimingRecords : NSObject
+
+@property (assign, readonly) NSUInteger max;
+@property (assign, readonly) NSUInteger count;
+@property (assign, readonly) NSTimeInterval average;
+
+- (EDTimingRecords *)initWithMax:(NSUInteger)max;
+- (void)add:(NSTimeInterval)interval;
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDTiming.m
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDTiming.m b/experiments/objcFramework/EDTiming.m
new file mode 100644
index 0000000..5e7c675
--- /dev/null
+++ b/experiments/objcFramework/EDTiming.m
@@ -0,0 +1,271 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import "EDTiming.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                          EDTimingEntry                                         //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation EDTimingEntry
+
+- (EDTimingEntry *)initWithName:(NSString *)name
+                         time:(NSTimeInterval)time
+                          min:(NSTimeInterval)min
+                          max:(NSTimeInterval)max
+{
+    if (!(self = [super init]))
+        return nil;
+    _name = [name copy];
+    _time = time;
+    _min = min;
+    _max = max;
+    return self;
+}
+
+- (EDTimingEntry *)initWithName:(NSString *)name time:(NSTimeInterval)time
+{
+    return [self initWithName: name time: time min: time max: time];
+}
+
+- (NSString *)description
+{
+    return [NSString stringWithFormat: @"%@: %dms", _name, (int)(_time*1000)];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                          EDTimingInfo                                          //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface EDTimingInfo()
+
+@property (strong) NSDate *lastTime;
+
+@end
+
+@implementation EDTimingInfo
+{
+    NSMutableArray *_entries;
+}
+
+- (EDTimingInfo *)initWithEntries:(NSMutableArray *)entries
+{
+    if (!(self = [super init]))
+        return nil;
+    _entries = entries;
+    for (EDTimingEntry *entry in _entries)
+        _total += entry.time;
+    return self;
+}
+
+- (EDTimingInfo *)init
+{
+    return [self initWithEntries: [NSMutableArray arrayWithCapacity: 0]];
+}
+
+- (void)start
+{
+    [_entries removeAllObjects];
+    self.lastTime = [NSDate date];
+}
+
+- (void)addEntry:(NSString *)name
+{
+    if (self.lastTime == nil)
+        [self start];
+
+    NSDate *now = [NSDate date];
+    NSTimeInterval interval = [now timeIntervalSinceDate: self.lastTime];
+    self.lastTime = now;
+    [_entries addObject: [[EDTimingEntry alloc] initWithName: name time: interval]];
+    _total += interval;
+}
+
+- (NSString *)description
+{
+    NSMutableString *result = [NSMutableString stringWithCapacity: 0];
+    for (EDTimingEntry *entry in _entries)
+        [result appendFormat: @"%@\n", entry];
+    [result appendFormat: @"Total: %dms", (int)(_total*1000)];
+    return result;
+}
+
+- (NSString *)descriptionWithTitle:(NSString *)title
+{
+    NSMutableString *result = [NSMutableString stringWithCapacity: 0];
+    [result appendFormat: @"%@\n",title];
+    for (EDTimingEntry *entry in _entries)
+        [result appendFormat: @"    %@\n", entry];
+    [result appendFormat: @"    Total: %dms", (int)(_total*1000)];
+    return result;
+}
+
+- (NSString *)detailedDescription
+{
+    // Use UTF8 strings here, since field width specifiers don't seem to work with %@
+    NSMutableString *result = [NSMutableString stringWithCapacity: 0];
+    [result appendFormat: @"%-40s %-8s %-8s %-8s\n", "Stage", "Avg", "Min", "Max"];
+    [result appendFormat: @"%-40s %-8s %-8s %-8s\n", "-----", "---", "---", "---"];
+    for (EDTimingEntry *entry in _entries) {
+        [result appendFormat: @"%-40s %-8d %-8d %-8d\n",
+         entry.name.UTF8String,
+         (int)(entry.time*1000),
+         (int)(entry.min*1000),
+         (int)(entry.max*1000)];
+    }
+    [result appendFormat: @"Total: %dms", (int)(_total*1000)];
+    return result;
+}
+
+- (BOOL)hasSameEntryNamesAs:(EDTimingInfo *)other
+{
+    if (self.entries.count != other.entries.count)
+        return NO;
+    for (NSUInteger i = 0; i < self.entries.count; i++) {
+        EDTimingEntry *selfEntry = [self.entries objectAtIndex: i];
+        EDTimingEntry *otherEntry = [other.entries objectAtIndex: i];
+        if (![selfEntry.name isEqualToString: otherEntry.name])
+            return NO;
+    }
+    return YES;
+}
+
++ (EDTimingInfo *)computeAverages:(NSArray *)timings
+{
+    if (timings.count == 0)
+        return nil;
+    EDTimingInfo *first = [timings objectAtIndex: 0];
+
+    for (EDTimingInfo *current in timings) {
+        if (![current hasSameEntryNamesAs: first])
+            return nil;
+    }
+
+    NSUInteger numEntries = first.entries.count;
+
+    NSMutableArray *minArray = [NSMutableArray arrayWithCapacity: 0];
+    NSMutableArray *maxArray = [NSMutableArray arrayWithCapacity: 0];
+    NSMutableArray *totalArray = [NSMutableArray arrayWithCapacity: 0];
+    for (NSUInteger i = 0; i < numEntries; i++) {
+        [minArray addObject: [NSNumber numberWithDouble: 0]];
+        [maxArray addObject: [NSNumber numberWithDouble: 0]];
+        [totalArray addObject: [NSNumber numberWithDouble: 0]];
+    }
+
+    for (EDTimingInfo *current in timings) {
+        for (NSUInteger i = 0; i < current.entries.count; i++) {
+            EDTimingEntry *entry = [current.entries objectAtIndex: i];
+
+            NSNumber *minVal = [minArray objectAtIndex: i];
+            if ((current == first) || (minVal.doubleValue > entry.min)) {
+                minVal = [NSNumber numberWithDouble: entry.min];
+                [minArray replaceObjectAtIndex: i withObject: minVal];
+            }
+
+            NSNumber *maxVal = [maxArray objectAtIndex: i];
+            if ((current == first) || (maxVal.doubleValue < entry.max)) {
+                maxVal = [NSNumber numberWithDouble: entry.max];
+                [maxArray replaceObjectAtIndex: i withObject: maxVal];
+            }
+
+            NSNumber *totalVal = [totalArray objectAtIndex: i];
+            totalVal = [NSNumber numberWithDouble: totalVal.doubleValue + entry.time];
+            [totalArray replaceObjectAtIndex: i withObject: totalVal];
+        }
+    }
+
+    NSMutableArray *results = [NSMutableArray arrayWithCapacity: 0];
+    for (NSUInteger i = 0; i < first.entries.count; i++) {
+        EDTimingEntry *entry = [first.entries objectAtIndex: i];
+        NSNumber *minVal = [minArray objectAtIndex: i];
+        NSNumber *maxVal = [maxArray objectAtIndex: i];
+        NSNumber *totalVal = [totalArray objectAtIndex: i];
+        EDTimingEntry *combined = [[EDTimingEntry alloc] initWithName: entry.name
+                                                             time: totalVal.doubleValue/timings.count
+                                                              min: minVal.doubleValue
+                                                              max: maxVal.doubleValue];
+        [results addObject: combined];
+    }
+
+    return [[EDTimingInfo alloc] initWithEntries: results];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                         EDTimingRecords                                        //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation EDTimingRecords
+{
+    NSTimeInterval *_values;
+}
+
+- (EDTimingRecords *)initWithMax:(NSUInteger)max
+{
+    if (!(self = [super init]))
+        return nil;
+    _max = max;
+    _count = 0;
+    _values = (NSTimeInterval *)malloc(_max*sizeof(NSTimeInterval));
+    return self;
+}
+
+- (void)dealloc
+{
+    free(_values);
+}
+
+- (void)add:(NSTimeInterval)interval
+{
+    if (_count == _max) {
+        for (NSUInteger i = 0; i < _max-1; i++)
+            _values[i] = _values[i+1];
+    }
+    else {
+        _count++;
+    }
+    _values[_count-1] = interval;
+}
+
+- (NSTimeInterval)average
+{
+    if (_count == 0)
+        return 0;
+
+    NSTimeInterval total = 0;
+    for (NSUInteger i = 0; i < _count; i++)
+        total += _values[i];
+    return total/_count;
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDUtil.h
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDUtil.h b/experiments/objcFramework/EDUtil.h
new file mode 100644
index 0000000..56c3606
--- /dev/null
+++ b/experiments/objcFramework/EDUtil.h
@@ -0,0 +1,41 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import <Foundation/Foundation.h>
+#import <Editor/DocFormats.h>
+
+extern int debugIndent;
+void debug(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);
+DFHashTable *HashTableFromNSDictionary(NSDictionary *dict);
+NSDictionary *NSDictionaryFromHashTable(DFHashTable *hashTable);
+NSDictionary *NSDictionaryFromNestedHashTable(DFHashTable *outerHash);
+NSString *DFFixStringEncoding(NSString *str);
+NSString *NSStringFromC(const char *cstr);
+void NSErrorSetFromDFError(NSError **nserror, DFError *dferror);
+void DFErrorReleaseToNSError(DFError *dferror, NSError **nserror);
+void DFErrorSetFromNSError(DFError **dferror, NSError *nserror);
+
+BOOL EDStringEquals(NSString *a, NSString *b);
+NSString *EDEncodeFontFamily(NSString *input);
+NSString *EDDecodeFontFamily(NSString *input);

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDUtil.m
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDUtil.m b/experiments/objcFramework/EDUtil.m
new file mode 100644
index 0000000..127e407
--- /dev/null
+++ b/experiments/objcFramework/EDUtil.m
@@ -0,0 +1,325 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import "EDUtil.h"
+#import <FileClient/FCError.h>
+#include <iconv.h>
+
+int debugIndent = 0;
+
+void debug(NSString *format, ...)
+{
+    for (int i = 0; i < debugIndent; i++)
+        printf("    ");
+    va_list ap;
+    va_start(ap,format);
+    NSString *message = [[NSString alloc] initWithFormat: format arguments: ap];
+    va_end(ap);
+    printf("%s",message.UTF8String);
+}
+
+DFHashTable *HashTableFromNSDictionary(NSDictionary *dict)
+{
+    if (dict == NULL)
+        return NULL;
+
+    DFHashTable *hashTable = DFHashTableNew((DFCopyFunction)strdup,(DFFreeFunction)free);
+    for (NSString *key in dict.allKeys) {
+        NSString *value = [dict objectForKey: key];
+        assert([value isKindOfClass: [NSString class]]);
+        DFHashTableAdd(hashTable,key.UTF8String,value.UTF8String);
+    }
+    return hashTable;
+}
+
+NSDictionary *NSDictionaryFromHashTable(DFHashTable *hashTable)
+{
+    if (hashTable == NULL)
+        return NULL;
+    NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity: 0];
+    const char **keys = DFHashTableCopyKeys(hashTable);
+    for (int i = 0; keys[i]; i++) {
+        const char *key = keys[i];
+        const char *value = DFHashTableLookup(hashTable,key);
+        [dict setObject: NSStringFromC(value) forKey: NSStringFromC(key)];
+    }
+    free(keys);
+    return dict;
+}
+
+NSDictionary *NSDictionaryFromNestedHashTable(DFHashTable *outerHash)
+{
+    NSMutableDictionary *outerDict = [NSMutableDictionary dictionaryWithCapacity: 0];
+    const char **allOuterKeys = DFHashTableCopyKeys(outerHash);
+    for (int outerIndex = 0; allOuterKeys[outerIndex]; outerIndex++) {
+        const char *outerKey = allOuterKeys[outerIndex];
+        DFHashTable *innerHash = DFHashTableLookup(outerHash,outerKey);
+        assert(innerHash != NULL);
+        NSDictionary *innerDict = NSDictionaryFromHashTable(innerHash);
+        [outerDict setObject: innerDict forKey: NSStringFromC(outerKey)];
+    }
+    free(allOuterKeys);
+    return outerDict;
+}
+
+#define UTF16IsLeadSurrogate(_c)  (((_c) >= 0xD800) && ((_c) <= 0xDBFF))
+#define UTF16IsTrailSurrogate(_c) (((_c) >= 0xDC00) && ((_c) <= 0xDFFF))
+
+typedef struct DFUTF16Buffer DFUTF16Buffer;
+
+struct DFUTF16Buffer {
+    size_t alloc;
+    size_t len;
+    char *data;
+};
+
+static void DFUTF16BufferInit(DFUTF16Buffer *buf, size_t alloc)
+{
+    buf->alloc = alloc;
+    buf->len = 0;
+    buf->data = (char *)malloc(buf->alloc*sizeof(char));
+}
+
+static void DFUTF16BufferDestroy(DFUTF16Buffer *buf)
+{
+    free(buf->data);
+}
+
+static void DFUTF16BufferExpand(DFUTF16Buffer *buf)
+{
+    buf->alloc = (buf->alloc == 0) ? 1 : buf->alloc*2;
+    buf->data = (char *)realloc(buf->data,buf->alloc*sizeof(char));
+}
+
+void DFCharEncodingConvert(iconv_t ic, void *data, size_t len, DFUTF16Buffer *output)
+{
+    iconv(ic,NULL,NULL,NULL,NULL); // Reset converter state
+
+    char *inbuf = (char *)data;
+    size_t inbytesleft = len;
+
+    output->len = 0;
+
+    while (inbytesleft > 0) {
+        const char *oldInbuf = inbuf;
+        char *outbuf = &output->data[output->len];
+        size_t outbytesleft = output->alloc - output->len;
+        size_t r = iconv(ic,&inbuf,&inbytesleft,&outbuf,&outbytesleft);
+        if (r == ((size_t)-1)) {
+            if ((errno == EILSEQ) || (errno == EINVAL)) {
+                // Invalid or incomplete multibyte sequence; skip it
+                inbuf++;
+                inbytesleft--;
+                iconv(ic,NULL,NULL,NULL,NULL); // Reset converter state
+
+                // We still want the output that was generated
+                output->len = outbuf - output->data;
+                assert(output->len <= output->alloc);
+
+                continue;
+            }
+        }
+
+        if (oldInbuf == inbuf) {
+            DFUTF16BufferExpand(output);
+        }
+        else {
+            output->len = outbuf - output->data;
+            assert(output->len <= output->alloc);
+        }
+    }
+}
+
+int DFCharEncodingFixChars(uint16_t *chars, NSUInteger len)
+{
+    int changed = 0;
+
+    NSUInteger i = 0;
+    while (i < len) {
+        if (UTF16IsLeadSurrogate(chars[i])) {
+            if ((i+1 < len) && UTF16IsTrailSurrogate(chars[i+1])) {
+                // VALID: Surrogate pair
+                i += 2;
+            }
+            else {
+                // INVALID: Missing trail surrogate
+                chars[i] = '?';
+                changed = 1;
+                i += 1;
+            }
+        }
+        else if (UTF16IsTrailSurrogate(chars[i])) {
+            // INVALID: Unexpected trail surrogate
+            chars[i] = '?';
+            changed = 1;
+            i += 1;
+        }
+        else {
+            // VALID: Single character
+            i += 1;
+        }
+    }
+
+    return changed;
+}
+
+NSString *DFCharEncodingFixNSString(NSString *str)
+{
+    NSUInteger len = str.length;
+    uint16_t *chars = (uint16_t *)malloc(len*sizeof(uint16_t));
+    [str getCharacters: chars range: NSMakeRange(0,len)];
+    if (DFCharEncodingFixChars(chars,len))
+        str = [NSString stringWithCharacters: chars length: len];
+    free(chars);
+    return str;
+}
+
+static void printHex(const void *data, size_t len)
+{
+    const char *chars = (const char *)data;
+    for (size_t i = 0; i < len; i++) {
+        printf("%02X ",(unsigned char)chars[i]);
+    }
+    printf("\n");
+}
+
+static void printChars(const void *data, size_t len)
+{
+    const char *chars = (const char *)data;
+    for (size_t i = 0; i < len; i++) {
+        if ((chars[i] >= 32) && (chars[i] <= 127))
+            printf("%c  ",chars[i]);
+        else
+            printf(".  ");
+    }
+    printf("\n");
+}
+
+void DFCharEncodingTest(void)
+{
+    iconv_t *UTF8toUCS2 = iconv_open("UCS-2-INTERNAL","UTF-8");
+    assert(UTF8toUCS2 != ((iconv_t)-1));
+    printf("u8tou16 = %p\n",UTF8toUCS2);
+
+    iconv_t *UCS2toUTF8 = iconv_open("UTF-8","UCS-2-INTERNAL");
+    assert(UCS2toUTF8 != ((iconv_t)-1));
+    printf("UCS2toUTF8 = %p\n",UCS2toUTF8);
+
+    char *input = "Hello 多语种多多网站";
+    size_t inputLen = strlen(input);
+    printf("%s\n",input);
+
+    printHex(input,inputLen);
+    printChars(input,inputLen);
+    printf("----\n");
+
+    NSString *str = [NSString stringWithCString: input encoding: NSUTF8StringEncoding];
+    uint16_t *chars = (uint16_t *)calloc(1,str.length*sizeof(uint16_t));
+    [str getCharacters: chars range: NSMakeRange(0,str.length)];
+    printHex(chars,str.length*sizeof(uint16_t));
+    printChars(chars,str.length*sizeof(uint16_t));
+    free(chars);
+
+    printf("----\n");
+    DFUTF16Buffer ucs2;
+    DFUTF16BufferInit(&ucs2,0);
+    DFCharEncodingConvert(UTF8toUCS2,input,inputLen,&ucs2);
+    printHex(ucs2.data,ucs2.len);
+    printChars(ucs2.data,ucs2.len);
+
+
+    printf("----\n");
+    DFUTF16Buffer utf8;
+    DFUTF16BufferInit(&utf8,0);
+    DFCharEncodingConvert(UCS2toUTF8,ucs2.data,ucs2.len,&utf8);
+    printHex(utf8.data,utf8.len);
+    printChars(utf8.data,utf8.len);
+
+    char *temp = (char*)malloc(utf8.len+1);
+    memcpy(temp,utf8.data,utf8.len);
+    temp[utf8.len] = '\0';
+    printf("%s\n",temp);
+    free(temp);
+    
+    DFUTF16BufferDestroy(&ucs2);
+    DFUTF16BufferDestroy(&utf8);
+}
+
+NSString *DFFixStringEncoding(NSString *str)
+{
+    return DFCharEncodingFixNSString(str);
+}
+
+NSString *NSStringFromC(const char *cstr)
+{
+    if (cstr == NULL)
+        return NULL;
+    else
+        return [NSString stringWithUTF8String: cstr];
+}
+
+void NSErrorSetFromDFError(NSError **nserror, DFError *dferror)
+{
+    if ((nserror == NULL) || (dferror == NULL))
+        return;
+    [NSError set: nserror format: @"%@", NSStringFromC(DFErrorMessage(&dferror))];
+}
+
+void DFErrorReleaseToNSError(DFError *dferror, NSError **nserror)
+{
+    NSErrorSetFromDFError(nserror,dferror);
+}
+
+void DFErrorSetFromNSError(DFError **dferror, NSError *nserror)
+{
+    if ((nserror == NULL) || (dferror == NULL))
+        return;
+    DFErrorFormat(dferror,"%s",FCErrorDescription(nserror).UTF8String);
+}
+
+BOOL EDStringEquals(NSString *a, NSString *b)
+{
+    if ((a == NULL) && (b == NULL))
+        return YES;
+    else if ((a != NULL) && (b != NULL))
+        return [a isEqualToString: b];
+    else
+        return NO;
+}
+
+NSString *EDEncodeFontFamily(NSString *input)
+{
+    char *cresult = CSSEncodeFontFamily(input.UTF8String);
+    NSString *nsresult = NSStringFromC(cresult);
+    free(cresult);
+    return nsresult;
+}
+
+NSString *EDDecodeFontFamily(NSString *input)
+{
+    char *cresult = CSSDecodeFontFamily(input.UTF8String);
+    NSString *nsresult = NSStringFromC(cresult);
+    free(cresult);
+    return nsresult;
+}

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDWordCount.h
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDWordCount.h b/experiments/objcFramework/EDWordCount.h
new file mode 100644
index 0000000..2d4ce5e
--- /dev/null
+++ b/experiments/objcFramework/EDWordCount.h
@@ -0,0 +1,42 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import <Foundation/Foundation.h>
+#import "EDScan.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                      EDWordCountOperation                                      //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface EDWordCountOperation : EDScanOperation
+
+@property (assign, readonly) int words;
+@property (assign, readonly) int chars;
+@property (assign, readonly) int paragraphs;
+
+- (EDScanOperation *)initWithEditor:(EDEditor *)editor;
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDWordCount.m
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDWordCount.m b/experiments/objcFramework/EDWordCount.m
new file mode 100644
index 0000000..e0ee55b
--- /dev/null
+++ b/experiments/objcFramework/EDWordCount.m
@@ -0,0 +1,57 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import "EDWordCount.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                      EDWordCountOperation                                      //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation EDWordCountOperation
+{
+    NSCharacterSet *_whitespace;
+}
+
+- (EDScanOperation *)initWithEditor:(EDEditor *)editor
+{
+    if (!(self = [super initWithEditor: editor]))
+        return nil;
+    _whitespace = [NSCharacterSet whitespaceAndNewlineCharacterSet];
+    return self;
+}
+
+- (void)processParagraph:(EDScanParagraph *)paragraph
+{
+    NSArray *components = [paragraph.text componentsSeparatedByCharactersInSet: _whitespace];
+    for (NSString *word in components) {
+        if (word.length > 0)
+            _words++;
+    }
+    _chars += paragraph.text.length;
+    _paragraphs++;
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/Editor.h
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/Editor.h b/experiments/objcFramework/Editor.h
new file mode 100644
index 0000000..26ad274
--- /dev/null
+++ b/experiments/objcFramework/Editor.h
@@ -0,0 +1,40 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import <Foundation/Foundation.h>
+
+#import <Editor/EDEditor.h>
+#import <Editor/EDFileFormat.h>
+#import <Editor/EDFindReplace.h>
+#import <Editor/EDGeometry.h>
+#import <Editor/EDJSInterface.h>
+#import <Editor/EDOutline.h>
+#import <Editor/EDSaveOperation.h>
+#import <Editor/EDScan.h>
+#import <Editor/EDScanResults.h>
+#import <Editor/EDSelectionFormatting.h>
+#import <Editor/EDStyle.h>
+#import <Editor/EDTiming.h>
+#import <Editor/EDUtil.h>
+#import <Editor/EDWordCount.h>


[3/4] incubator-corinthia git commit: Add Objective C framework code from UX Write

Posted by pm...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDGeometry.m
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDGeometry.m b/experiments/objcFramework/EDGeometry.m
new file mode 100644
index 0000000..76b3db1
--- /dev/null
+++ b/experiments/objcFramework/EDGeometry.m
@@ -0,0 +1,119 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import "EDGeometry.h"
+
+static CGRect CGRectFromNumberDict(NSDictionary *dict)
+{
+    if ((dict == nil) || ![dict isKindOfClass: [NSDictionary class]])
+        return CGRectZero;
+
+    NSNumber *x = [dict objectForKey: @"x"];
+    NSNumber *y = [dict objectForKey: @"y"];
+    NSNumber *width = [dict objectForKey: @"width"];
+    NSNumber *height = [dict objectForKey: @"height"];
+
+    if ((x == nil) || ![x isKindOfClass: [NSNumber class]] ||
+        (y == nil) || ![y isKindOfClass: [NSNumber class]] ||
+        (width == nil) || ![width isKindOfClass: [NSNumber class]] ||
+        (height == nil) || ![height isKindOfClass: [NSNumber class]])
+        return CGRectZero;
+
+    return CGRectMake(x.doubleValue,y.doubleValue,width.doubleValue,height.doubleValue);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                         EDItemGeometry                                         //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation EDItemGeometry
+
+- (EDItemGeometry *)initWithContentRect:(CGRect)contentRect
+                             fullRect:(CGRect)fullRect
+                           parentRect:(CGRect)parentRect
+                           hasCaption:(BOOL)hasCaption
+                         columnWidths:(NSArray *)columnWidths
+{
+    if (!(self = [super init]))
+        return nil;
+    _contentRect = contentRect;
+    _fullRect = fullRect;
+    _parentRect = parentRect;
+    _hasCaption = hasCaption;
+    _columnWidths = [columnWidths copy];
+    if (_columnWidths != nil)
+        _columnOffsets = [self.class computeOffsets: _columnWidths];
+    return self;
+}
+
+static BOOL isArrayOfType(NSArray *array, Class cls)
+{
+    if ((array == nil) || ![array isKindOfClass: [NSArray class]])
+        return NO;
+    for (id obj in array) {
+        if (![obj isKindOfClass: cls])
+            return NO;
+    }
+    return YES;
+}
+
++ (EDItemGeometry *)fromDict:(NSDictionary *)dict
+{
+    CGRect contentRect = CGRectFromNumberDict([dict objectForKey: @"contentRect"]);
+    CGRect fullRect = CGRectFromNumberDict([dict objectForKey: @"fullRect"]);
+    CGRect parentRect = CGRectFromNumberDict([dict objectForKey: @"parentRect"]);
+    BOOL hasCaption = ([dict objectForKey: @"hasCaption"] != nil);
+    NSArray *columnWidths = [dict objectForKey: @"columnWidths"];
+    if (CGRectIsEmpty(contentRect))
+        return nil;
+    if (CGRectIsEmpty(fullRect))
+        return nil;
+    if (CGRectIsEmpty(parentRect))
+        return nil;
+    if ((columnWidths != nil) && !isArrayOfType(columnWidths,[NSNumber class]))
+        return nil;
+    return [[EDItemGeometry alloc] initWithContentRect: contentRect
+                                            fullRect: fullRect
+                                          parentRect: parentRect
+                                          hasCaption: hasCaption
+                                        columnWidths: columnWidths];
+}
+
++ (NSArray *)computeOffsets:(NSArray *)widths
+{
+    if (widths == nil)
+        return nil;
+    NSMutableArray *offsets = [NSMutableArray arrayWithCapacity: 0];
+    CGFloat total = 0;
+    for (NSNumber *pct in widths) {
+        [offsets addObject: [NSNumber numberWithDouble: total]];
+        total += pct.doubleValue;
+    }
+    [offsets addObject: [NSNumber numberWithDouble: total]];
+    return offsets;
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDHTMLTidy.h
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDHTMLTidy.h b/experiments/objcFramework/EDHTMLTidy.h
new file mode 100644
index 0000000..d2995b8
--- /dev/null
+++ b/experiments/objcFramework/EDHTMLTidy.h
@@ -0,0 +1,42 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import <Foundation/Foundation.h>
+#import "DFPlatform.h"
+#import "DFTidyWrapper.h"
+
+@class EDEditor;
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                           EDHTMLTidy                                           //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface EDHTMLTidy : NSObject
+
++ (void)tidy:(NSData *)input isXHTML:(BOOL)isXHTML editor:(EDEditor *)editor
+  completion:(void (^)(NSData *output, NSError *error))completion;
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDHTMLTidy.m
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDHTMLTidy.m b/experiments/objcFramework/EDHTMLTidy.m
new file mode 100644
index 0000000..2ba2d64
--- /dev/null
+++ b/experiments/objcFramework/EDHTMLTidy.m
@@ -0,0 +1,58 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import "EDHTMLTidy.h"
+#import "EDEditor.h"
+#import "EDUtil.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                           EDHTMLTidy                                           //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation EDHTMLTidy
+
++ (void)tidy:(NSData *)input isXHTML:(BOOL)isXHTML editor:(EDEditor *)editor
+  completion:(void (^)(NSData *output, NSError *error))completion
+{
+    __block NSData *output = nil;
+    __block NSError *error = nil;
+    [editor.system runInBackground: ^{
+        DFError *dferr = NULL;
+        DFBuffer *inbuf = DFBufferNew();
+        DFBuffer *outbuf = DFBufferNew();
+        DFBufferAppendData(inbuf,input.bytes,input.length);
+        if (!DFHTMLTidy(inbuf,outbuf,isXHTML,&dferr))
+            DFErrorReleaseToNSError(dferr,&error);
+        else
+            output = [NSData dataWithBytes: outbuf->data length: outbuf->len];
+        DFBufferRelease(inbuf);
+        DFBufferRelease(outbuf);
+    } completion:^{
+        completion(output,error);
+    }];
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDJSInterface.h
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDJSInterface.h b/experiments/objcFramework/EDJSInterface.h
new file mode 100644
index 0000000..0979e08
--- /dev/null
+++ b/experiments/objcFramework/EDJSInterface.h
@@ -0,0 +1,365 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import <Foundation/Foundation.h>
+#import <CoreGraphics/CoreGraphics.h>
+
+@class JSAutoCorrect;
+@class JSChangeTracking;
+@class JSClipboard;
+@class JSCursor;
+@class JSEquations;
+@class JSFigures;
+@class JSFormatting;
+@class JSInput;
+@class JSLists;
+@class JSMain;
+@class JSMetadata;
+@class JSOutline;
+@class JSPreview;
+@class JSScan;
+@class JSSelection;
+@class JSStyles;
+@class JSTables;
+@class JSUndoManager;
+@class JSViewport;
+@class EDScanParagraph;
+
+@interface JSError : NSError
+
+@property (copy, readonly) NSString *type;
+@property (copy, readonly) NSString *message;
+@property (copy, readonly) NSString *operation;
+@property (copy, readonly) NSString *html;
+
+@end
+
+@protocol JSInterfaceDelegate
+
+- (void)jsAddOutlineItem:(NSString *)itemId type:(NSString *)type title:(NSString *)title;
+- (void)jsUpdateOutlineItem:(NSString *)itemId title:(NSString *)title;
+- (void)jsRemoveOutlineItem:(NSString *)itemId;
+- (void)jsOutlineUpdated;
+- (void)jsSetCursorX:(int)x y:(int)y width:(int)width height:(int)height;
+- (void)jsSetSelectionHandlesX1:(int)x1 y1:(int)y1 height1:(int)height1
+                             x2:(int)x2 y2:(int)y2 height2:(int)height2;
+- (void)jsSetTableSelectionX:(int)x y:(int)y width:(int)width height:(int)height;
+- (void)jsSetSelectionBoundsLeft:(int)left top:(int)top right:(int)right bottom:(int)bottom;
+- (void)jsClearSelectionHandlesAndCursor;
+- (void)jsUpdateAutoCorrect;
+
+@end
+
+@protocol JSEvaluator
+
+- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
+
+@end
+
+@interface JSInterface : NSObject
+
+@property (weak) NSObject<JSEvaluator> *evaluator;
+@property (weak) NSObject<JSInterfaceDelegate> *delegate;
+@property (assign) BOOL jsInitialised;
+@property (strong) JSAutoCorrect *autoCorrect;
+@property (strong) JSChangeTracking *changeTracking;
+@property (strong) JSClipboard *clipboard;
+@property (strong) JSCursor *cursor;
+@property (strong) JSEquations *equations;
+@property (strong) JSFigures *figures;
+@property (strong) JSFormatting *formatting;
+@property (strong) JSInput *input;
+@property (strong) JSLists *lists;
+@property (strong) JSMain *main;
+@property (strong) JSMetadata *metadata;
+@property (strong) JSOutline *outline;
+@property (strong) JSPreview *preview;
+@property (strong) JSScan *scan;
+@property (strong) JSSelection *selection;
+@property (strong) JSStyles *styles;
+@property (strong) JSTables *tables;
+@property (strong) JSUndoManager *undoManager;
+@property (strong) JSViewport *viewport;
+@property (copy) NSString *currentOperation;
+@property (strong) JSError *error;
+@property (assign) BOOL documentModified;
+
+- (JSInterface *)initWithEvaluator:(NSObject<JSEvaluator> *)evaluator;
+- (BOOL)initJavaScriptWidth:(int)width textScale:(int)textScale cssURL:(NSString *)cssURL
+             clientRectsBug:(BOOL)clientRectsBug;
+- (void)printStatistics;
+
+@end
+
+@interface JSModule : NSObject
+
+@property (assign) JSInterface *js;
+
+- (JSModule *)initWithJS:(JSInterface *)js;
+
+@end
+
+// Functions implemented in AutoCorrect.js
+
+@interface JSAutoCorrect : JSModule
+
+- (void)correctPreceding:(int)numChars word:(NSString *)replacement confirmed:(BOOL)confirmed;
+- (NSDictionary *)getCorrection;
+- (NSDictionary *)getCorrectionCoords;
+- (void)acceptCorrection;
+- (void)replaceCorrection:(NSString *)replacement;
+
+@end
+
+// Functions implemented in ChangeTracking.js
+
+@interface JSChangeTracking : JSModule
+- (BOOL)showChanges;
+- (BOOL)trackChanges;
+- (void)setShowChanges:(BOOL)showChanges;
+- (void)setTrackChanges:(BOOL)trackChanges;
+@end
+
+// Functions implemented in Clipboard.js
+
+@interface JSClipboard : JSModule
+- (NSDictionary *)clipboardCut;
+- (NSDictionary *)clipboardCopy;
+- (void)pasteHTML:(NSString *)html;
+- (void)pasteText:(NSString *)text;
+@end
+
+// Functions implemented in Cursor.js
+
+@interface JSCursor : JSModule
+- (NSString *)positionCursorX:(int)x y:(int)y wordBoundary:(BOOL)wordBoundary;
+- (CGRect)getCursorPosition;
+- (void)moveLeft;
+- (void)moveRight;
+- (void)moveToStartOfDocument;
+- (void)moveToEndOfDocument;
+- (void)insertReference:(NSString *)itemId;
+- (void)insertLinkWithText:(NSString *)text URL:(NSString *)URL;
+- (void)insertCharacter:(unichar)character allowInvalidPos:(BOOL)allowInvalidPos;
+- (void)deleteCharacter;
+- (void)enterPressed;
+- (NSString *)getPrecedingWord;
+- (NSDictionary *)getLinkProperties;
+- (void)setLinkProperties:(NSDictionary *)properties;
+- (void)setReferenceTarget:(NSString *)itemId;
+- (void)insertFootnote:(NSString *)content;
+- (void)insertEndnote:(NSString *)content;
+@end
+
+// Functions implemented in Equations.js
+
+@interface JSEquations : JSModule
+- (void)insertEquation;
+@end
+
+// Functions implemented in Figures.js
+
+@interface JSFigures : JSModule
+- (void)insertFigure:(NSString *)filename width:(NSString *)width
+            numbered:(BOOL)numbered caption:(NSString *)caption;
+- (NSString *)getSelectedFigureId;
+- (NSDictionary *)getProperties:(NSString *)itemId;
+- (void)setProperties:(NSString *)itemId width:(NSString *)width src:(NSString *)src;
+- (NSDictionary *)getGeometry:(NSString *)itemId;
+@end
+
+// Functions implemented in Formatting.js
+
+@interface JSFormatting : JSModule
+- (NSDictionary *)getFormatting;
+- (void)applyFormattingChangesStyle:(NSString *)style properties:(NSDictionary *)properties;
+@end
+
+// Functions implemented in Input.js
+
+@interface JSInput : JSModule
+- (void)removePosition:(int)posId;
+
+// UITextInput methods
+- (NSString *)textInRangeStartId:(int)startId startAdjust:(int)startAdjust
+                           endId:(int)endId endAdjust:(int)endAdjust;
+- (void)replaceRangeStart:(int)startId end:(int)endId withText:(NSString *)text;
+- (NSDictionary *)selectedTextRange;
+- (void)setSelectedTextRangeStart:(int)startId end:(int)endId;
+- (NSDictionary *)markedTextRange;
+- (void)setMarkedText:(NSString *)text startOffset:(int)startOffset endOffset:(int)endOffset;
+- (void)unmarkText;
+- (BOOL)forwardSelectionAffinity;
+- (void)setForwardSelectionAffinity:(BOOL)forwardSelectionAffinity;
+- (int)positionFromPosition:(int)posId offset:(int)offset;
+- (int)positionFromPosition:(int)posId inDirection:(NSString *)direction offset:(int)offset;
+- (int)comparePosition:(int)positionId toPosition:(int)otherId;
+- (int)offsetFromPosition:(int)fromPosition toPosition:(int)toPosition;
+- (int)positionWithinRangeStart:(int)startId end:(int)endId farthestInDirection:(NSString *)direction;
+- (NSDictionary *)characterRangeByExtendingPosition:(int)positionId inDirection:(NSString *)direction;
+- (NSDictionary *)firstRectForRangeStart:(int)startId end:(int)endId;
+- (NSDictionary *)caretRectForPosition:(int)posId;
+- (int)closestPositionToPointX:(int)x y:(int)y;
+- (int)closestPositionToPointX:(int)x y:(int)y withinRangeStart:(int)startId end:(int)endId;
+- (NSDictionary *)characterRangeAtPointX:(int)x y:(int)y;
+- (int)positionWithinRangeStart:(int)startId end:(int)endId atCharacterOffset:(int)offset;
+- (int)characterOffsetOfPosition:(int)positionId withinRangeStart:(int)startId end:(int)endId;
+
+// UITextInputTokenizer methods
+- (BOOL)isPosition:(int)posId atBoundary:(NSString *)granularity inDirection:(NSString *)direction;
+- (BOOL)isPosition:(int)posId withinTextUnit:(NSString *)granularity inDirection:(NSString *)direction;
+- (int)positionFromPosition:(int)posId toBoundary:(NSString *)granularity inDirection:(NSString *)direction;
+- (NSDictionary *)rangeEnclosingPosition:(int)posId withGranularity:(NSString *)granularity inDirection:(NSString *)direction;
+@end
+
+// Functions implemented in Lists.js
+
+@interface JSLists : JSModule
+- (void)increaseIndent;
+- (void)decreaseIndent;
+- (void)clearList;
+- (void)setUnorderedList;
+- (void)setOrderedList;
+@end
+
+// Functions implemented in Main.js
+
+@interface JSMain : JSModule
+- (NSString *)getLanguage;
+- (void)setLanguage:(NSString *)language;
+- (NSString *)setGenerator:(NSString *)generator;
+- (BOOL)prepareForSave;
+- (NSString *)getHTML;
+- (BOOL)isEmptyDocument;
+@end
+
+// Functions implemented in Metadata.js
+
+@interface JSMetadata : JSModule
+- (NSDictionary *)getMetadata;
+- (void)setMetadata:(NSDictionary *)metadata;
+@end
+
+// Functions implemented in Outline.js
+
+@interface JSOutline : JSModule
+- (NSDictionary *)getOutline;
+- (void)moveSection:(NSString *)sectionId parentId:(NSString *)parentId nextId:(NSString *)nextId;
+- (void)deleteItem:(NSString *)itemId;
+- (void)goToItem:(NSString *)itemId;
+- (void)scheduleUpdateStructure;
+- (void)set:(NSString *)itemId numbered:(BOOL)numbered;
+- (void)set:(NSString *)itemId title:(NSString *)title;
+- (void)insertTableOfContents;
+- (void)insertListOfFigures;
+- (void)insertListOfTables;
+- (void)setPrintMode:(BOOL)printMode;
+- (NSDictionary *)examinePrintLayout:(int)pageHeight;
+- (BOOL)detectSectionNumbering;
+- (NSDictionary *)findUsedStyles;
+@end
+
+// Functions implemented in Preview.js
+
+@interface JSPreview : JSModule
+- (void)showForStyle:(NSString *)styleId uiName:(NSString *)uiName title:(NSString *)title;
+@end
+
+// Functions implemented in Scan.js
+
+@interface JSScan : JSModule
+- (void)reset;
+- (EDScanParagraph *)next;
+- (int)addMatchStart:(int)start end:(int)end;
+- (void)showMatch:(int)matchId;
+- (void)replaceMatch:(int)matchId with:(NSString *)text;
+- (void)removeMatch:(int)matchId;
+- (void)goToMatch:(int)matchId;
+@end
+
+// Functions implemented in Selection.js
+
+@interface JSSelection : JSModule
+- (void)update;
+- (void)selectAll;
+- (void)selectParagraph;
+- (void)selectWordAtCursor;
+- (NSString *)dragSelectionBeginX:(int)x y:(int)y selectWord:(BOOL)selectWord;
+- (NSString *)dragSelectionUpdateX:(int)x y:(int)y selectWord:(BOOL)selectWord;
+- (NSString *)moveStartLeft;
+- (NSString *)moveStartRight;
+- (NSString *)moveEndLeft;
+- (NSString *)moveEndRight;
+- (void)setSelectionStartAtCoordsX:(int)x y:(int)y;
+- (void)setSelectionEndAtCoordsX:(int)x y:(int)y;
+- (void)setTableSelectionEdge:(NSString *)edge atCoordsX:(int)x y:(int)y;
+- (void)print;
+@end
+
+// Functions implemented in Styles.js
+
+@interface JSStyles : JSModule
+- (NSString *)getCSSText;
+- (void)setCSSText:(NSString *)cssText rules:(NSDictionary *)rules;
+- (NSString *)paragraphClass;
+- (void)setParagraphClass:(NSString *)paragraphClass;
+@end
+
+// Functions implemented in Tables.js
+
+@interface JSTables : JSModule
+- (void)insertTableRows:(int)rows cols:(int)cols width:(NSString *)width numbered:(BOOL)numbered
+                caption:(NSString *)caption className:(NSString *)className;
+- (void)addAdjacentRow;
+- (void)addAdjacentColumn;
+- (void)removeAdjacentRow;
+- (void)removeAdjacentColumn;
+- (void)clearCells;
+- (void)mergeCells;
+- (void)splitSelection;
+- (NSString *)getSelectedTableId;
+- (NSDictionary *)getProperties:(NSString *)itemId;
+- (void)setProperties:(NSString *)itemId width:(NSString *)width;
+- (void)set:(NSString *)itemId colWidths:(NSArray *)colWidths;
+- (NSDictionary *)getGeometry:(NSString *)itemId;
+@end
+
+// Functions implemented in UndoManager.js
+
+@interface JSUndoManager : JSModule
+- (int)getLength;
+- (int)getIndex;
+- (void)setIndex:(int)index;
+- (void)undo;
+- (void)redo;
+- (void)newGroup:(NSString *)name;
+- (NSString *)groupType;
+@end
+
+// Functions implemented in Viewport.js
+
+@interface JSViewport : JSModule
+- (void)setViewportWidth:(int)width;
+- (void)setTextScale:(int)textScale;
+@end


[2/4] incubator-corinthia git commit: Add Objective C framework code from UX Write

Posted by pm...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDJSInterface.m
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDJSInterface.m b/experiments/objcFramework/EDJSInterface.m
new file mode 100644
index 0000000..aeae062
--- /dev/null
+++ b/experiments/objcFramework/EDJSInterface.m
@@ -0,0 +1,1775 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import "EDJSInterface.h"
+#import "EDScan.h"
+#import "EDUtil.h"
+#import <FileClient/FCError.h>
+#import <FileClient/FCUtil.h>
+
+typedef enum {
+    AutoCorrect_correctPrecedingWord,
+    AutoCorrect_getCorrection,
+    AutoCorrect_getCorrectionCoords,
+    AutoCorrect_acceptCorrection,
+    AutoCorrect_replaceCorrection,
+    ChangeTracking_showChanges,
+    ChangeTracking_trackChanges,
+    ChangeTracking_setShowChanges,
+    ChangeTracking_setTrackChanges,
+    Clipboard_cut,
+    Clipboard_copy,
+    Clipboard_pasteHTML,
+    Clipboard_pasteText,
+    Cursor_positionCursor,
+    Cursor_getCursorPosition,
+    Cursor_moveLeft,
+    Cursor_moveRight,
+    Cursor_moveToStartOfDocument,
+    Cursor_moveToEndOfDocument,
+    Cursor_insertReference,
+    Cursor_insertLink,
+    Cursor_insertCharacter,
+    Cursor_deleteCharacter,
+    Cursor_enterPressed,
+    Cursor_getPrecedingWord,
+    Cursor_getLinkProperties,
+    Cursor_setLinkProperties,
+    Cursor_setReferenceTarget,
+    Cursor_insertFootnote,
+    Cursor_insertEndnote,
+    Editor_getBackMessages,
+    Equations_insertEquation,
+    Figures_insertFigure,
+    Figures_getSelectedFigureId,
+    Figures_getProperties,
+    Figures_setProperties,
+    Figures_getGeometry,
+    Formatting_getFormatting,
+    Formatting_applyFormattingChanges,
+    Input_removePosition,
+    Input_textInRange,
+    Input_replaceRange,
+    Input_selectedTextRange,
+    Input_setSelectedTextRange,
+    Input_markedTextRange,
+    Input_setMarkedText,
+    Input_unmarkText,
+    Input_forwardSelectionAffinity,
+    Input_setForwardSelectionAffinity,
+    Input_positionFromPositionOffset,
+    Input_positionFromPositionInDirectionOffset,
+    Input_comparePositionToPosition,
+    Input_offsetFromPositionToPosition,
+    Input_positionWithinRangeFarthestInDirection,
+    Input_characterRangeByExtendingPositionInDirection,
+    Input_firstRectForRange,
+    Input_caretRectForPosition,
+    Input_closestPositionToPoint,
+    Input_closestPositionToPointWithinRange,
+    Input_characterRangeAtPoint,
+    Input_positionWithinRangeAtCharacterOffset,
+    Input_characterOffsetOfPositionWithinRange,
+    Input_isPositionAtBoundaryGranularityInDirection,
+    Input_isPositionWithinTextUnitInDirection,
+    Input_positionFromPositionToBoundaryInDirection,
+    Input_rangeEnclosingPositionWithGranularityInDirection,
+    Lists_increaseIndent,
+    Lists_decreaseIndent,
+    Lists_clearList,
+    Lists_setUnorderedList,
+    Lists_setOrderedList,
+    Main_getLanguage,
+    Main_setLanguage,
+    Main_setGenerator,
+    Main_init,
+    Main_getErrorReportingInfo,
+    Main_execute,
+    Main_prepareForSave,
+    Main_getHTML,
+    Main_isEmptyDocument,
+    Metadata_getMetadata,
+    Metadata_setMetadata,
+    Outline_moveSection,
+    Outline_deleteItem,
+    Outline_goToItem,
+    Outline_scheduleUpdateStructure,
+    Outline_setNumbered,
+    Outline_setTitle,
+    Outline_getOutline,
+    Outline_insertTableOfContents,
+    Outline_insertListOfFigures,
+    Outline_insertListOfTables,
+    Outline_setPrintMode,
+    Outline_examinePrintLayout,
+    Outline_detectSectionNumbering,
+    Outline_findUsedStyles,
+    Preview_showForStyle,
+    Scan_reset,
+    Scan_next,
+    Scan_addMatch,
+    Scan_showMatch,
+    Scan_replaceMatch,
+    Scan_removeMatch,
+    Scan_goToMatch,
+    Selection_update,
+    Selection_selectAll,
+    Selection_selectParagraph,
+    Selection_selectWordAtCursor,
+    Selection_dragSelectionBegin,
+    Selection_dragSelectionUpdate,
+    Selection_moveStartLeft,
+    Selection_moveStartRight,
+    Selection_moveEndLeft,
+    Selection_moveEndRight,
+    Selection_setSelectionStartAtCoords,
+    Selection_setSelectionEndAtCoords,
+    Selection_setTableSelectionEdgeAtCoords,
+    Selection_print,
+    Styles_getCSSText,
+    Styles_setCSSText,
+    Styles_getParagraphClass,
+    Styles_setParagraphClass,
+    Tables_insertTable,
+    Tables_addAdjacentRow,
+    Tables_addAdjacentColumn,
+    Tables_removeAdjacentRow,
+    Tables_removeAdjacentColumn,
+    Tables_clearCells,
+    Tables_mergeCells,
+    Tables_splitSelection,
+    Tables_getSelectedTableId,
+    Tables_getProperties,
+    Tables_setProperties,
+    Tables_setColWidths,
+    Tables_getGeometry,
+    UndoManager_getLength,
+    UndoManager_getIndex,
+    UndoManager_setIndex,
+    UndoManager_undo,
+    UndoManager_redo,
+    UndoManager_newGroup,
+    UndoManager_groupType,
+    Viewport_setViewportWidth,
+    Viewport_setTextScale,
+    JSInterfaceFunctionCount,
+} JSInterfaceFunction;
+
+static BOOL functionModifiesDocument(JSInterfaceFunction fun)
+{
+    switch (fun)
+    {
+        case AutoCorrect_correctPrecedingWord:
+        case AutoCorrect_acceptCorrection:
+        case AutoCorrect_replaceCorrection:
+        case ChangeTracking_setShowChanges:
+        case ChangeTracking_setTrackChanges:
+        case Clipboard_cut:
+        case Clipboard_pasteHTML:
+        case Clipboard_pasteText:
+        case Cursor_insertReference:
+        case Cursor_insertLink:
+        case Cursor_insertCharacter:
+        case Cursor_deleteCharacter:
+        case Cursor_enterPressed:
+        case Cursor_setLinkProperties:
+        case Cursor_setReferenceTarget:
+        case Cursor_insertFootnote:
+        case Cursor_insertEndnote:
+        case Equations_insertEquation:
+        case Figures_insertFigure:
+        case Figures_setProperties:
+        case Formatting_applyFormattingChanges:
+        case Lists_increaseIndent:
+        case Lists_decreaseIndent:
+        case Lists_clearList:
+        case Lists_setUnorderedList:
+        case Lists_setOrderedList:
+        case Main_setLanguage:
+        case Main_prepareForSave:
+        case Metadata_setMetadata:
+        case Outline_moveSection:
+        case Outline_deleteItem:
+        case Outline_scheduleUpdateStructure:
+        case Outline_setNumbered:
+        case Outline_setTitle:
+        case Outline_insertTableOfContents:
+        case Outline_insertListOfFigures:
+        case Outline_insertListOfTables:
+        case Outline_setPrintMode:
+        case Outline_examinePrintLayout:
+        case Scan_replaceMatch:
+        case Styles_setCSSText:
+        case Tables_insertTable:
+        case Tables_addAdjacentRow:
+        case Tables_addAdjacentColumn:
+        case Tables_removeAdjacentRow:
+        case Tables_removeAdjacentColumn:
+        case Tables_clearCells:
+        case Tables_mergeCells:
+        case Tables_splitSelection:
+        case Tables_setProperties:
+        case Tables_setColWidths:
+        case UndoManager_undo:
+        case UndoManager_redo:
+            return true;
+        case AutoCorrect_getCorrection:
+        case AutoCorrect_getCorrectionCoords:
+        case ChangeTracking_showChanges:
+        case ChangeTracking_trackChanges:
+        case Clipboard_copy:
+        case Cursor_positionCursor:
+        case Cursor_getCursorPosition:
+        case Cursor_moveLeft:
+        case Cursor_moveRight:
+        case Cursor_moveToStartOfDocument:
+        case Cursor_moveToEndOfDocument:
+        case Cursor_getPrecedingWord:
+        case Cursor_getLinkProperties:
+        case Editor_getBackMessages:
+        case Figures_getSelectedFigureId:
+        case Figures_getProperties:
+        case Figures_getGeometry:
+        case Formatting_getFormatting:
+        case Input_removePosition:
+        case Input_textInRange:
+        case Input_replaceRange:
+        case Input_selectedTextRange:
+        case Input_setSelectedTextRange:
+        case Input_markedTextRange:
+        case Input_setMarkedText:
+        case Input_unmarkText:
+        case Input_forwardSelectionAffinity:
+        case Input_setForwardSelectionAffinity:
+        case Input_positionFromPositionOffset:
+        case Input_positionFromPositionInDirectionOffset:
+        case Input_comparePositionToPosition:
+        case Input_offsetFromPositionToPosition:
+        case Input_positionWithinRangeFarthestInDirection:
+        case Input_characterRangeByExtendingPositionInDirection:
+        case Input_firstRectForRange:
+        case Input_caretRectForPosition:
+        case Input_closestPositionToPoint:
+        case Input_closestPositionToPointWithinRange:
+        case Input_characterRangeAtPoint:
+        case Input_positionWithinRangeAtCharacterOffset:
+        case Input_characterOffsetOfPositionWithinRange:
+        case Input_isPositionAtBoundaryGranularityInDirection:
+        case Input_isPositionWithinTextUnitInDirection:
+        case Input_positionFromPositionToBoundaryInDirection:
+        case Input_rangeEnclosingPositionWithGranularityInDirection:
+        case Main_getLanguage:
+        case Main_setGenerator:
+        case Main_init:
+        case Main_getErrorReportingInfo:
+        case Main_execute:
+        case Main_getHTML:
+        case Main_isEmptyDocument:
+        case Metadata_getMetadata:
+        case Outline_getOutline:
+        case Outline_goToItem:
+        case Outline_detectSectionNumbering:
+        case Outline_findUsedStyles:
+        case Preview_showForStyle:
+        case Scan_reset:
+        case Scan_next:
+        case Scan_addMatch:
+        case Scan_showMatch:
+        case Scan_removeMatch:
+        case Scan_goToMatch:
+        case Selection_update:
+        case Selection_selectAll:
+        case Selection_selectParagraph:
+        case Selection_selectWordAtCursor:
+        case Selection_dragSelectionBegin:
+        case Selection_dragSelectionUpdate:
+        case Selection_moveStartLeft:
+        case Selection_moveStartRight:
+        case Selection_moveEndLeft:
+        case Selection_moveEndRight:
+        case Selection_setSelectionStartAtCoords:
+        case Selection_setSelectionEndAtCoords:
+        case Selection_setTableSelectionEdgeAtCoords:
+        case Selection_print:
+        case Styles_getCSSText:
+        case Styles_getParagraphClass:
+        case Styles_setParagraphClass:
+        case Tables_getSelectedTableId:
+        case Tables_getProperties:
+        case Tables_getGeometry:
+        case UndoManager_getLength:
+        case UndoManager_getIndex:
+        case UndoManager_setIndex:
+        case UndoManager_newGroup:
+        case UndoManager_groupType:
+        case Viewport_setViewportWidth:
+        case Viewport_setTextScale:
+        case JSInterfaceFunctionCount:
+            return false;
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                             JSError                                            //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation JSError
+
+- (JSError *)initWithType:(NSString *)type
+                  message:(NSString *)message
+                operation:(NSString *)operation
+                     html:(NSString *)html
+{
+    if (!(self = [super init]))
+        return nil;
+    _type = [type copy];
+    _message = [message copy];
+    _operation = [operation copy];
+    _html = [html copy];
+    return self;
+}
+
+- (NSString *)description
+{
+    NSMutableString *errorInfo = [NSMutableString stringWithCapacity: 0];
+    [errorInfo appendFormat: @"%@\n", _message];
+    [errorInfo appendFormat: @"Operation: %@\n", _operation];
+    [errorInfo appendFormat: @"\n"];
+    [errorInfo appendFormat: @"HTML:\n%@\n", _html];
+    return errorInfo;
+}
+
+- (NSString *)localizedDescription
+{
+    return [self description];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                           JSInterface                                          //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation JSInterface
+{
+    NSUInteger _numCalls[JSInterfaceFunctionCount];
+    NSTimeInterval _timeUsed[JSInterfaceFunctionCount];
+    NSTimeInterval _totalTimeUsed;
+}
+
+- (JSInterface *)initWithEvaluator:(NSObject<JSEvaluator> *)evaluator
+{
+    if (!(self = [super init]))
+        return nil;
+    _evaluator = evaluator;
+
+    _autoCorrect = [[JSAutoCorrect alloc] initWithJS: self];
+    _changeTracking = [[JSChangeTracking alloc] initWithJS: self];
+    _clipboard = [[JSClipboard alloc] initWithJS: self];
+    _cursor = [[JSCursor alloc] initWithJS: self];
+    _equations = [[JSEquations alloc] initWithJS: self];
+    _figures = [[JSFigures alloc] initWithJS: self];
+    _formatting = [[JSFormatting alloc] initWithJS: self];
+    _input = [[JSInput alloc] initWithJS: self];
+    _lists = [[JSLists alloc] initWithJS: self];
+    _main = [[JSMain alloc] initWithJS: self];
+    _metadata = [[JSMetadata alloc] initWithJS: self];
+    _outline = [[JSOutline alloc] initWithJS: self];
+    _preview = [[JSPreview alloc] initWithJS: self];
+    _scan = [[JSScan alloc] initWithJS: self];
+    _selection = [[JSSelection alloc] initWithJS: self];
+    _styles = [[JSStyles alloc] initWithJS: self];
+    _tables = [[JSTables alloc] initWithJS: self];
+    _undoManager = [[JSUndoManager alloc] initWithJS: self];
+    _viewport = [[JSViewport alloc] initWithJS: self];
+
+    return self;
+}
+
+- (void)dealloc
+{
+    _autoCorrect.js = nil;
+    _changeTracking.js = nil;
+    _clipboard.js = nil;
+    _cursor.js = nil;
+    _equations.js = nil;
+    _figures.js = nil;
+    _formatting.js = nil;
+    _input.js = nil;
+    _lists.js = nil;
+    _main.js = nil;
+    _metadata.js = nil;
+    _outline.js = nil;
+    _preview.js = nil;
+    _scan.js = nil;
+    _selection.js = nil;
+    _styles.js = nil;
+    _tables.js = nil;
+    _undoManager.js = nil;
+    _viewport.js = nil;
+}
+
+- (BOOL)initJavaScriptWidth:(int)width textScale:(int)textScale cssURL:(NSString *)cssURL
+             clientRectsBug:(BOOL)clientRectsBug
+{
+    // Special case javascript call to Main_init(). We don't use the normal mechanism here because
+    // at this point the JS interface has not been initialised, and the check for this in
+    // executeJavaScriptWithJSONResult would cause it to fail.
+    NSString *code = [NSString stringWithFormat: @"interface_functions[%d](%d,%d,\"%@\",%@)",
+                      Main_init, width, textScale, cssURL, clientRectsBug ? @"true" : @"false"];
+    NSString *result = [_evaluator stringByEvaluatingJavaScriptFromString: code];
+    [self jsCallCompleted];
+    if ([@"true" isEqualToString: result]) {
+        return YES;
+    }
+    else {
+        self.currentOperation = @"JavaScript initialisation";
+        [self reportErrorWithType: nil format: @"%@", result];
+        self.currentOperation = nil;
+        return NO;
+    }
+}
+
+- (void)jsCallCompleted
+{
+    // Process pending callbacks
+    NSString *getBackMessages = [NSString stringWithFormat: @"interface_functions[%d]()",
+                                 Editor_getBackMessages];
+    NSString *str = [_evaluator stringByEvaluatingJavaScriptFromString: getBackMessages];
+    NSData *data = [str dataUsingEncoding: NSUTF8StringEncoding];
+    NSArray *array = [NSJSONSerialization JSONObjectWithData: data options: 0 error: nil];
+    for (NSArray *message in array)
+        [self dispatchCallbackMessage: message];
+}
+
+- (NSString *)operationStringWithFunction:(JSInterfaceFunction)function nargs:(int)nargs arguments:(va_list)ap
+{
+    NSMutableString *operation = [NSMutableString stringWithCapacity: 0];
+    [operation appendFormat: @"interface_functions[%d](", function];
+    NSObject *arg;
+    for (int argno = 0; argno < nargs; argno++) {
+        arg = va_arg(ap,NSObject*);
+        if (argno > 0)
+            [operation appendString: @","];
+        if (arg == nil) {
+            [operation appendString: @"null"];
+        }
+        else if ([arg isKindOfClass: [NSNumber class]]) {
+            [operation appendString: [arg description]];
+        }
+        else if ([arg isKindOfClass: [NSNull class]]) {
+            [operation appendString: @"null"];
+        }
+        else if ([arg isKindOfClass: [NSArray class]] || [arg isKindOfClass: [NSDictionary class]]) {
+            NSError *err = nil;
+            NSData *data = [NSJSONSerialization dataWithJSONObject: arg options: 0 error: &err];
+            if (data == nil) {
+                [NSException raise: @"NSJSONSerialization"
+                            format: @"executeJavaScript %d: cannot serialise argument %d to JSON: %@",
+                                    function, argno, FCErrorDescription(err)];
+            }
+            else {
+                NSString *str = [[NSString alloc] initWithBytes: data.bytes length: data.length
+                                                       encoding: NSUTF8StringEncoding];
+                [operation appendString: str];
+            }
+        }
+        else {
+            char *quoted = DFQuote(arg.description.UTF8String);
+            NSString *nsQuoted = [NSString stringWithUTF8String: quoted];
+            [operation appendString: nsQuoted];
+            free(quoted);
+        }
+    }
+    [operation appendString: @")"];
+    return operation;
+}
+
+- (id)executeJavaScriptWithJSONResult:(BOOL)jsonResult function:(JSInterfaceFunction)function nargs:(int)nargs arguments:(va_list)ap
+{
+    NSDate *start = [NSDate date];
+    if (!_jsInitialised) {
+        [NSException raise: @"JavaScriptNotInitialised"
+                    format: @"Yet to receive a jsInterfaceInitFinished from JavaScript code"];
+    }
+    self.currentOperation = [self operationStringWithFunction: function nargs: nargs arguments: ap];
+    id result;
+    if (!jsonResult) {
+        NSString *code = [NSString stringWithFormat: @"interface_functions[%d](function() { return %@; });",
+                                                     Main_execute, self.currentOperation];
+        result = [_evaluator stringByEvaluatingJavaScriptFromString: code];
+        [self jsCallCompleted];
+    }
+    else {
+        NSString *code = [NSString stringWithFormat: @"interface_functions[%d](function() { return JSON.stringify(%@); });",
+                                                     Main_execute, self.currentOperation];
+
+        NSString *str = [_evaluator stringByEvaluatingJavaScriptFromString: code];
+        [self jsCallCompleted];
+
+        if ([str isEqualToString: @"null"]) {
+            result = nil;
+        }
+        else {
+            NSData *data = [str dataUsingEncoding: NSUTF8StringEncoding];
+            NSError *err = nil;
+            result = [NSJSONSerialization JSONObjectWithData: data options: 0 error: &err];
+            if (result == nil) {
+                [self reportErrorWithType: nil format: @"Cannot deserialise JSON result \"%@\": %@",
+                 str, FCErrorDescription(err)];
+            }
+        }
+    }
+
+    NSDate *end = [NSDate date];
+    NSTimeInterval interval = [end timeIntervalSinceDate: start];
+    _numCalls[function]++;
+    _timeUsed[function] += interval;
+    _totalTimeUsed += interval;
+
+    self.currentOperation = nil;
+    if (functionModifiesDocument(function))
+        self.documentModified = YES;
+    return result;
+}
+
+- (void)printStatistics
+{
+    if (_totalTimeUsed == 0.0)
+        return;
+
+    printf("\n");
+    printf("%-10s %-10s %-10s %-10s\n","Function","Calls","Time","Time pct");
+
+    NSMutableArray *order = [NSMutableArray arrayWithCapacity: 0];
+    for (NSUInteger i = 0; i < JSInterfaceFunctionCount; i++)
+        [order addObject: [NSNumber numberWithUnsignedInteger: i]];
+    [order sortUsingComparator:^NSComparisonResult(NSNumber *n1, NSNumber *n2) {
+        NSUInteger i1 = n1.unsignedIntValue;
+        NSUInteger i2 = n2.unsignedIntValue;
+        if (_timeUsed[i1] < _timeUsed[i2])
+            return NSOrderedAscending;
+        else if (_timeUsed[i1] > _timeUsed[i2])
+            return NSOrderedDescending;
+        else
+            return NSOrderedSame;
+    }];
+    for (NSNumber *n in order) {
+        NSUInteger i = n.unsignedIntValue;
+        double pct = 100*(_timeUsed[i]/_totalTimeUsed);
+        if (pct >= 0.01) {
+            printf("%-10d %-10d %-10d %.2f%%\n",(int)i,(int)_numCalls[i],(int)(_timeUsed[i]*1000),pct);
+        }
+    }
+    printf("totalTimeUsed = %.2fms\n",1000*_totalTimeUsed);
+}
+
+- (id)executeJavaScriptJSON:(JSInterfaceFunction)function nargs:(int)nargs, ...
+{
+    va_list ap;
+    va_start(ap,nargs);
+    NSString *result = (NSString *)[self executeJavaScriptWithJSONResult: YES
+                                                                function: function
+                                                                   nargs: nargs
+                                                               arguments: ap];
+    va_end(ap);
+    return result;
+}
+
+- (NSString *)executeJavaScript:(JSInterfaceFunction)function nargs:(int)nargs, ...
+{
+    va_list ap;
+    va_start(ap,nargs);
+    NSString *result = (NSString *)[self executeJavaScriptWithJSONResult: NO
+                                                                function: function
+                                                                   nargs: nargs
+                                                               arguments: ap];
+    va_end(ap);
+    return result;
+}
+
+- (void)dispatchCallbackMessage:(NSArray*)array
+{
+    if (array.count == 0)
+        return;
+
+    if (![[array objectAtIndex: 0] isKindOfClass: [NSString class]])
+        return;
+
+    NSString *functionName = [array objectAtIndex: 0];
+
+    if ([functionName isEqualToString: @"debug"] &&
+        ([array count] == 2) &&
+        [[array objectAtIndex: 1] isKindOfClass: [NSString class]]) {
+        NSString *message = [array objectAtIndex: 1];
+        debug(@"%@\n",message);
+    }
+    else if ([functionName isEqualToString: @"addOutlineItem"] &&
+             [_delegate respondsToSelector: @selector(jsAddOutlineItem:type:title:)] &&
+             ([array count] == 4) &&
+             [[array objectAtIndex: 1] isKindOfClass: [NSString class]] &&
+             [[array objectAtIndex: 2] isKindOfClass: [NSString class]]) {
+        // 3 could be a a string or null
+        NSString *title = [array objectAtIndex: 3];
+        if ([title isKindOfClass: [NSNull class]])
+            title = nil;
+        [_delegate jsAddOutlineItem: [array objectAtIndex: 1]
+                              type: [array objectAtIndex: 2]
+                             title: title];
+    }
+    else if ([functionName isEqualToString: @"updateOutlineItem"] &&
+             [_delegate respondsToSelector: @selector(jsUpdateOutlineItem:title:)] &&
+             ([array count] == 3) &&
+             [[array objectAtIndex: 1] isKindOfClass: [NSString class]] &&
+             [[array objectAtIndex: 2] isKindOfClass: [NSString class]]) {
+        [_delegate jsUpdateOutlineItem: [array objectAtIndex: 1] title: [array objectAtIndex: 2]];
+    }
+    else if ([functionName isEqualToString: @"removeOutlineItem"] &&
+             [_delegate respondsToSelector: @selector(jsRemoveOutlineItem:)] &&
+             ([array count] == 2) &&
+             [[array objectAtIndex: 1] isKindOfClass: [NSString class]]) {
+        [_delegate jsRemoveOutlineItem: [array objectAtIndex: 1]];
+    }
+    else if ([functionName isEqualToString: @"outlineUpdated"] &&
+             [_delegate respondsToSelector: @selector(jsOutlineUpdated)] &&
+             [array count] == 1) {
+        [_delegate jsOutlineUpdated];
+    }
+    else if ([functionName isEqualToString: @"setSelectionHandles"] &&
+             [_delegate respondsToSelector: @selector(jsSetSelectionHandlesX1:y1:height1:x2:y2:height2:)] &&
+             ([array count] == 7) &&
+             ([[array objectAtIndex: 1] isKindOfClass: [NSNumber class]]) &&
+             ([[array objectAtIndex: 2] isKindOfClass: [NSNumber class]]) &&
+             ([[array objectAtIndex: 3] isKindOfClass: [NSNumber class]]) &&
+             ([[array objectAtIndex: 4] isKindOfClass: [NSNumber class]]) &&
+             ([[array objectAtIndex: 5] isKindOfClass: [NSNumber class]]) &&
+             ([[array objectAtIndex: 6] isKindOfClass: [NSNumber class]])) {
+        [_delegate jsSetSelectionHandlesX1: ((NSNumber *)[array objectAtIndex: 1]).intValue
+                                       y1: ((NSNumber *)[array objectAtIndex: 2]).intValue
+                                  height1: ((NSNumber *)[array objectAtIndex: 3]).intValue
+                                       x2: ((NSNumber *)[array objectAtIndex: 4]).intValue
+                                       y2: ((NSNumber *)[array objectAtIndex: 5]).intValue
+                                  height2: ((NSNumber *)[array objectAtIndex: 6]).intValue];
+    }
+    else if ([functionName isEqualToString: @"setTableSelection"] &&
+             [_delegate respondsToSelector: @selector(jsSetTableSelectionX:y:width:height:)] &&
+             ([array count] == 5) &&
+             ([[array objectAtIndex: 1] isKindOfClass: [NSNumber class]]) &&
+             ([[array objectAtIndex: 2] isKindOfClass: [NSNumber class]]) &&
+             ([[array objectAtIndex: 3] isKindOfClass: [NSNumber class]]) &&
+             ([[array objectAtIndex: 4] isKindOfClass: [NSNumber class]])) {
+        [_delegate jsSetTableSelectionX: ((NSNumber *)[array objectAtIndex: 1]).intValue
+                                     y: ((NSNumber *)[array objectAtIndex: 2]).intValue
+                                 width: ((NSNumber *)[array objectAtIndex: 3]).intValue
+                                height: ((NSNumber *)[array objectAtIndex: 4]).intValue];
+    }
+    else if ([functionName isEqualToString: @"setCursor"] &&
+             [_delegate respondsToSelector: @selector(jsSetCursorX:y:width:height:)] &&
+             ([array count] == 5) &&
+             ([[array objectAtIndex: 1] isKindOfClass: [NSNumber class]]) &&
+             ([[array objectAtIndex: 2] isKindOfClass: [NSNumber class]]) &&
+             ([[array objectAtIndex: 3] isKindOfClass: [NSNumber class]]) &&
+             ([[array objectAtIndex: 4] isKindOfClass: [NSNumber class]])) {
+        [_delegate jsSetCursorX: ((NSNumber *)[array objectAtIndex: 1]).intValue
+                             y: ((NSNumber *)[array objectAtIndex: 2]).intValue
+                         width: ((NSNumber *)[array objectAtIndex: 3]).intValue
+                        height: ((NSNumber *)[array objectAtIndex: 4]).intValue];
+    }
+    else if ([functionName isEqualToString: @"clearSelectionHandlesAndCursor"] &&
+             [_delegate respondsToSelector: @selector(jsClearSelectionHandlesAndCursor)] &&
+             ([array count] == 1)) {
+        [_delegate jsClearSelectionHandlesAndCursor];
+    }
+    else if ([functionName isEqualToString: @"setSelectionBounds"] &&
+             [_delegate respondsToSelector: @selector(jsSetSelectionBoundsLeft:top:right:bottom:)] &&
+             ([array count] == 5) &&
+             ([[array objectAtIndex: 1] isKindOfClass: [NSNumber class]]) &&
+             ([[array objectAtIndex: 2] isKindOfClass: [NSNumber class]]) &&
+             ([[array objectAtIndex: 3] isKindOfClass: [NSNumber class]]) &&
+             ([[array objectAtIndex: 4] isKindOfClass: [NSNumber class]])) {
+        [_delegate jsSetSelectionBoundsLeft: ((NSNumber *)[array objectAtIndex: 1]).intValue
+                                       top: ((NSNumber *)[array objectAtIndex: 2]).intValue
+                                     right: ((NSNumber *)[array objectAtIndex: 3]).intValue
+                                    bottom: ((NSNumber *)[array objectAtIndex: 4]).intValue];
+    }
+    else if ([functionName isEqualToString: @"updateAutoCorrect"] &&
+             [_delegate respondsToSelector: @selector(jsUpdateAutoCorrect)]) {
+        [_delegate jsUpdateAutoCorrect];
+    }
+    else if ([functionName isEqualToString: @"error"] &&
+             (array.count == 3) &&
+             [[array objectAtIndex: 1] isKindOfClass: [NSString class]] &&
+             [[array objectAtIndex: 2] isKindOfClass: [NSString class]]) {
+        NSString *message = [array objectAtIndex: 1];
+        NSString *type = [array objectAtIndex: 2];
+        if (type.length == 0)
+            type = nil;
+        [self reportErrorWithType: type format: @"%@", message];
+    }
+}
+
+- (void)reportErrorWithType:(NSString *)type format:(NSString *)format, ...
+{
+    if (self.error != nil)
+        return;
+
+    va_list ap;
+    va_start(ap,format);
+    NSString *basicError = [[NSString alloc] initWithFormat: format arguments: ap];
+    va_end(ap);
+
+    NSString *getErrorReportingInfo = [NSString stringWithFormat: @"interface_functions[%d]()",
+                                       Main_getErrorReportingInfo];
+    NSString *html = [_evaluator stringByEvaluatingJavaScriptFromString: getErrorReportingInfo];
+
+    self.error = [[JSError alloc] initWithType: type
+                                       message: basicError
+                                     operation: self.currentOperation
+                                          html: html];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                            JSModule                                            //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation JSModule
+
+- (JSModule *)initWithJS:(JSInterface *)js
+{
+    if (!(self = [super init]))
+        return nil;
+    _js = js;
+    return self;
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                          JSAutoCorrect                                         //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation JSAutoCorrect
+
+- (void)correctPreceding:(int)numChars word:(NSString *)replacement confirmed:(BOOL)confirmed;
+{
+    [self.js executeJavaScript: AutoCorrect_correctPrecedingWord nargs: 3,
+     [NSNumber numberWithInt: numChars], replacement, [NSNumber numberWithBool: confirmed]];
+}
+
+- (NSDictionary *)getCorrection
+{
+    return [self.js executeJavaScriptJSON: AutoCorrect_getCorrection nargs: 0];
+}
+
+- (NSDictionary *)getCorrectionCoords
+{
+    return [self.js executeJavaScriptJSON: AutoCorrect_getCorrectionCoords nargs: 0];
+}
+
+- (void)acceptCorrection
+{
+    [self.js executeJavaScript: AutoCorrect_acceptCorrection nargs: 0];
+}
+
+- (void)replaceCorrection:(NSString *)replacement
+{
+    [self.js executeJavaScript: AutoCorrect_replaceCorrection nargs: 1, replacement];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                        JSChangeTracking                                        //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation JSChangeTracking
+
+- (BOOL)showChanges
+{
+    NSString *result = [self.js executeJavaScript: ChangeTracking_showChanges nargs: 0];
+    return [result isEqualToString: @"true"];
+}
+
+- (BOOL)trackChanges
+{
+    NSString *result = [self.js executeJavaScript: ChangeTracking_trackChanges nargs: 0];
+    return [result isEqualToString: @"true"];
+}
+
+- (void)setShowChanges:(BOOL)showChanges
+{
+    [self.js executeJavaScript: ChangeTracking_setShowChanges
+                         nargs: 1, [NSNumber numberWithBool: showChanges]];
+}
+
+- (void)setTrackChanges:(BOOL)trackChanges
+{
+    [self.js executeJavaScript: ChangeTracking_setTrackChanges
+                         nargs: 1, [NSNumber numberWithBool: trackChanges]];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                           JSClipboard                                          //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation JSClipboard
+
+// The copy operation is called clipboardCopy to keep the static analyzer happy, because it thinks
+// that it relates to making a copy of the object and reports a memory leak. The cut operation
+// is named similarly just for consistency.
+
+- (NSDictionary *)clipboardCut
+{
+    return [self.js executeJavaScriptJSON: Clipboard_cut nargs: 0];
+}
+
+- (NSDictionary *)clipboardCopy
+{
+    return [self.js executeJavaScriptJSON: Clipboard_copy nargs: 0];
+}
+
+- (void)pasteHTML:(NSString *)html
+{
+    [self.js executeJavaScript: Clipboard_pasteHTML nargs: 1, html];
+}
+
+- (void)pasteText:(NSString *)text
+{
+    [self.js executeJavaScript: Clipboard_pasteText nargs: 1, text];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                            JSCursor                                            //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Functions implemented in Cursor.js
+
+@implementation JSCursor
+
+- (NSString *)positionCursorX:(int)x y:(int)y wordBoundary:(BOOL)wordBoundary
+{
+    return [self.js executeJavaScript: Cursor_positionCursor nargs: 3,
+            [NSNumber numberWithInt: x], [NSNumber numberWithInt: y],
+            [NSNumber numberWithBool: wordBoundary]];
+}
+
+- (CGRect)getCursorPosition
+{
+    NSDictionary *result = [self.js executeJavaScriptJSON: Cursor_getCursorPosition nargs: 0];
+    return CGRectMake(NSDictionaryGetInt(result,@"x"),
+                      NSDictionaryGetInt(result,@"y"),
+                      NSDictionaryGetInt(result,@"width"),
+                      NSDictionaryGetInt(result,@"height"));
+}
+
+- (void)moveLeft
+{
+    [self.js executeJavaScript: Cursor_moveLeft nargs: 0];
+}
+
+- (void)moveRight
+{
+    [self.js executeJavaScript: Cursor_moveRight nargs: 0];
+}
+
+- (void)moveToStartOfDocument
+{
+    [self.js executeJavaScript: Cursor_moveToStartOfDocument nargs: 0];
+}
+
+- (void)moveToEndOfDocument
+{
+    [self.js executeJavaScript: Cursor_moveToEndOfDocument nargs: 0];
+}
+
+- (void)insertReference:(NSString *)itemId
+{
+    [self.js executeJavaScript: Cursor_insertReference nargs: 1, itemId];
+}
+
+- (void)insertLinkWithText:(NSString *)text URL:(NSString *)URL
+{
+    [self.js executeJavaScript: Cursor_insertLink nargs: 2, text, URL];
+}
+
+- (void)insertCharacter:(unichar)character allowInvalidPos:(BOOL)allowInvalidPos
+{
+    NSString *str = [NSString stringWithFormat: @"%C", character];
+    [self.js executeJavaScript: Cursor_insertCharacter nargs: 2, str,
+     [NSNumber numberWithBool: allowInvalidPos]];
+}
+
+- (void)deleteCharacter
+{
+    [self.js executeJavaScript: Cursor_deleteCharacter nargs: 0];
+}
+
+- (void)enterPressed
+{
+    [self.js executeJavaScript: Cursor_enterPressed nargs: 0];
+}
+
+- (NSString *)getPrecedingWord
+{
+    return [self.js executeJavaScript: Cursor_getPrecedingWord nargs: 0];
+}
+
+- (NSDictionary *)getLinkProperties
+{
+    return [self.js executeJavaScriptJSON: Cursor_getLinkProperties nargs: 0];
+}
+
+- (void)setLinkProperties:(NSDictionary *)properties
+{
+    [self.js executeJavaScript: Cursor_setLinkProperties nargs: 1, properties];
+}
+
+- (void)setReferenceTarget:(NSString *)itemId
+{
+    [self.js executeJavaScript: Cursor_setReferenceTarget nargs: 1, itemId];
+}
+
+- (void)insertFootnote:(NSString *)content
+{
+    [self.js executeJavaScript: Cursor_insertFootnote nargs: 1, content];
+}
+
+- (void)insertEndnote:(NSString *)content
+{
+    [self.js executeJavaScript: Cursor_insertEndnote nargs: 1, content];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                            Equations                                           //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation JSEquations
+
+- (void)insertEquation
+{
+    [self.js executeJavaScript: Equations_insertEquation nargs: 0];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                            JSFigures                                           //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation JSFigures
+
+- (void)insertFigure:(NSString *)filename width:(NSString *)width
+            numbered:(BOOL)numbered caption:(NSString *)caption
+{
+    [self.js executeJavaScript: Figures_insertFigure nargs: 4,
+     filename, width, [NSNumber numberWithBool: numbered], caption];
+}
+
+- (NSString *)getSelectedFigureId
+{
+    return [self.js executeJavaScript: Figures_getSelectedFigureId nargs: 0];
+}
+
+- (NSDictionary *)getProperties:(NSString *)itemId
+{
+    return [self.js executeJavaScriptJSON: Figures_getProperties nargs: 1, itemId];
+}
+
+- (void)setProperties:(NSString *)itemId width:(NSString *)width src:(NSString *)src
+{
+    [self.js executeJavaScript: Figures_setProperties nargs: 3, itemId, width, src];
+}
+
+- (NSDictionary *)getGeometry:(NSString *)itemId
+{
+    return [self.js executeJavaScriptJSON: Figures_getGeometry nargs: 1, itemId];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                          JSFormatting                                          //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Functions implemented in Formatting.js
+
+@implementation JSFormatting
+
+- (NSDictionary *)getFormatting
+{
+    return [self.js executeJavaScriptJSON: Formatting_getFormatting nargs: 0];
+}
+
+- (void)applyFormattingChangesStyle:(NSString *)style properties:(NSDictionary *)properties
+{
+    [self.js executeJavaScript: Formatting_applyFormattingChanges nargs: 2, style, properties];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                             JSInput                                            //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation JSInput
+
+- (void)removePosition:(int)posId
+{
+    [self.js executeJavaScript: Input_removePosition nargs: 1, [NSNumber numberWithInt: posId]];
+}
+
+- (NSString *)textInRangeStartId:(int)startId startAdjust:(int)startAdjust
+                           endId:(int)endId endAdjust:(int)endAdjust
+{
+    return [self.js executeJavaScript: Input_textInRange nargs: 4,
+            [NSNumber numberWithInt: startId],
+            [NSNumber numberWithInt: startAdjust],
+            [NSNumber numberWithInt: endId],
+            [NSNumber numberWithInt: endAdjust]];
+}
+
+- (void)replaceRangeStart:(int)startId end:(int)endId withText:(NSString *)text
+{
+    [self.js executeJavaScript: Input_replaceRange nargs: 3,
+     [NSNumber numberWithInt: startId], [NSNumber numberWithInt: endId], text];
+}
+
+- (NSDictionary *)selectedTextRange
+{
+    return [self.js executeJavaScriptJSON: Input_selectedTextRange nargs: 0];
+}
+
+- (void)setSelectedTextRangeStart:(int)startId end:(int)endId
+{
+    [self.js executeJavaScript: Input_setSelectedTextRange nargs: 2,
+     [NSNumber numberWithInt: startId], [NSNumber numberWithInt: endId]];
+}
+
+- (NSDictionary *)markedTextRange
+{
+    return [self.js executeJavaScriptJSON: Input_markedTextRange nargs: 0];
+}
+
+- (void)setMarkedText:(NSString *)text startOffset:(int)startOffset endOffset:(int)endOffset
+{
+    [self.js executeJavaScript: Input_setMarkedText nargs: 3,
+     text, [NSNumber numberWithInt: startOffset], [NSNumber numberWithInt: endOffset]];
+}
+
+- (void)unmarkText
+{
+    [self.js executeJavaScript: Input_unmarkText nargs: 0];
+}
+
+- (BOOL)forwardSelectionAffinity
+{
+    NSString *result = [self.js executeJavaScript: Input_forwardSelectionAffinity nargs: 0];
+    return [result isEqualToString: @"true"];
+}
+
+- (void)setForwardSelectionAffinity:(BOOL)forwardSelectionAffinity
+{
+    [self.js executeJavaScript: Input_setForwardSelectionAffinity nargs: 1,
+     [NSNumber numberWithBool: forwardSelectionAffinity]];
+}
+
+- (int)positionFromPosition:(int)posId offset:(int)offset
+{
+    NSString *result = [self.js executeJavaScript: Input_positionFromPositionOffset nargs: 2,
+                        [NSNumber numberWithInt: posId], [NSNumber numberWithInt: offset]];
+    return result.intValue;
+}
+
+- (int)positionFromPosition:(int)posId inDirection:(NSString *)direction offset:(int)offset
+{
+    NSString *result = [self.js executeJavaScript: Input_positionFromPositionInDirectionOffset nargs: 3,
+                        [NSNumber numberWithInt: posId], direction, [NSNumber numberWithInt: offset]];
+    return result.intValue;
+}
+
+- (int)comparePosition:(int)positionId toPosition:(int)otherId
+{
+    NSString *result = [self.js executeJavaScript: Input_comparePositionToPosition nargs: 2,
+                        [NSNumber numberWithInt: positionId], [NSNumber numberWithInt: otherId]];
+    return result.intValue;
+}
+
+- (int)offsetFromPosition:(int)fromPosition toPosition:(int)toPosition
+{
+    NSString *result = [self.js executeJavaScript: Input_offsetFromPositionToPosition nargs: 2,
+                        [NSNumber numberWithInt: fromPosition], [NSNumber numberWithInt: toPosition]];
+    return result.intValue;
+}
+
+- (int)positionWithinRangeStart:(int)startId end:(int)endId farthestInDirection:(NSString *)direction
+{
+    NSString *result = [self.js executeJavaScript: Input_positionWithinRangeFarthestInDirection nargs: 3,
+                        [NSNumber numberWithInt: startId], [NSNumber numberWithInt: endId], direction];
+    return result.intValue;
+}
+
+- (NSDictionary *)characterRangeByExtendingPosition:(int)positionId inDirection:(NSString *)direction
+{
+    return [self.js executeJavaScriptJSON: Input_characterRangeByExtendingPositionInDirection nargs: 2,
+            [NSNumber numberWithInt: positionId], direction];
+}
+
+- (NSDictionary *)firstRectForRangeStart:(int)startId end:(int)endId
+{
+    return [self.js executeJavaScriptJSON: Input_firstRectForRange nargs: 2,
+            [NSNumber numberWithInt: startId], [NSNumber numberWithInt: endId]];
+}
+
+- (NSDictionary *)caretRectForPosition:(int)posId
+{
+    return [self.js executeJavaScriptJSON: Input_caretRectForPosition nargs: 1,
+            [NSNumber numberWithInt: posId]];
+}
+
+- (int)closestPositionToPointX:(int)x y:(int)y
+{
+    NSString *result = [self.js executeJavaScript: Input_closestPositionToPoint nargs: 2,
+                        [NSNumber numberWithInt: x], [NSNumber numberWithInt: y]];
+    return result.intValue;
+}
+
+- (int)closestPositionToPointX:(int)x y:(int)y withinRangeStart:(int)startId end:(int)endId
+{
+    NSString *result = [self.js executeJavaScript: Input_closestPositionToPointWithinRange nargs: 4,
+                        [NSNumber numberWithInt: x], [NSNumber numberWithInt: y],
+                        [NSNumber numberWithInt: startId], [NSNumber numberWithInt: endId]];
+    return result.intValue;
+}
+
+- (NSDictionary *)characterRangeAtPointX:(int)x y:(int)y
+{
+    return [self.js executeJavaScriptJSON: Input_characterRangeAtPoint nargs: 2,
+            [NSNumber numberWithInt: x], [NSNumber numberWithInt: y]];
+}
+
+- (int)positionWithinRangeStart:(int)startId end:(int)endId atCharacterOffset:(int)offset
+{
+    NSString *result = [self.js executeJavaScript: Input_positionWithinRangeAtCharacterOffset nargs: 3,
+                        [NSNumber numberWithInt: startId], [NSNumber numberWithInt: endId],
+                        [NSNumber numberWithInt: offset]];
+    return result.intValue;
+}
+
+- (int)characterOffsetOfPosition:(int)positionId withinRangeStart:(int)startId end:(int)endId
+{
+    NSString *result = [self.js executeJavaScript: Input_characterOffsetOfPositionWithinRange nargs: 3,
+                        [NSNumber numberWithInt: positionId],
+                        [NSNumber numberWithInt: startId], [NSNumber numberWithInt: endId]];
+    return result.intValue;
+}
+
+// UITextInputTokenizer methods
+
+- (BOOL)isPosition:(int)posId atBoundary:(NSString *)granularity inDirection:(NSString *)direction
+{
+    NSString *result = [self.js executeJavaScript: Input_isPositionAtBoundaryGranularityInDirection nargs: 3,
+                        [NSNumber numberWithInt: posId], granularity, direction];
+    return [result isEqualToString: @"true"];
+}
+
+- (BOOL)isPosition:(int)posId withinTextUnit:(NSString *)granularity inDirection:(NSString *)direction
+{
+    NSString *result = [self.js executeJavaScript: Input_isPositionWithinTextUnitInDirection nargs: 3,
+                        [NSNumber numberWithInt: posId], granularity, direction];
+    return [result isEqualToString: @"true"];
+}
+
+- (int)positionFromPosition:(int)posId toBoundary:(NSString *)granularity inDirection:(NSString *)direction
+{
+    NSString *result = [self.js executeJavaScript: Input_positionFromPositionToBoundaryInDirection nargs: 3,
+                        [NSNumber numberWithInt: posId], granularity, direction];
+    return result.intValue;
+}
+
+- (NSDictionary *)rangeEnclosingPosition:(int)posId withGranularity:(NSString *)granularity inDirection:(NSString *)direction
+{
+    return [self.js executeJavaScriptJSON: Input_rangeEnclosingPositionWithGranularityInDirection nargs: 3,
+            [NSNumber numberWithInt: posId], granularity, direction];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                             JSLists                                            //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Functions implemented in Lists.js
+
+@implementation JSLists
+
+- (void)increaseIndent
+{
+    [self.js executeJavaScript: Lists_increaseIndent nargs: 0];
+}
+
+- (void)decreaseIndent
+{
+    [self.js executeJavaScript: Lists_decreaseIndent nargs: 0];
+}
+
+- (void)clearList
+{
+    [self.js executeJavaScript: Lists_clearList nargs: 0];
+}
+
+- (void)setUnorderedList
+{
+    [self.js executeJavaScript: Lists_setUnorderedList nargs: 0];
+}
+
+- (void)setOrderedList
+{
+    [self.js executeJavaScript: Lists_setOrderedList nargs: 0];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                             JSMain                                             //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Functions implemented in Main.js
+
+@implementation JSMain
+
+- (NSString *)getLanguage
+{
+    return [self.js executeJavaScript: Main_getLanguage nargs: 0];
+}
+
+- (void)setLanguage:(NSString *)language
+{
+    [self.js executeJavaScript: Main_setLanguage nargs: 1, language];
+}
+
+- (NSString *)setGenerator:(NSString *)generator
+{
+    return [self.js executeJavaScript: Main_setGenerator nargs: 1, generator];
+}
+
+- (BOOL)prepareForSave
+{
+    NSString *result = [self.js executeJavaScript: Main_prepareForSave nargs: 0];
+    return [@"true" isEqualToString: result];
+}
+
+- (NSString *)getHTML;
+{
+    return [self.js executeJavaScript: Main_getHTML nargs: 0];
+}
+
+- (BOOL)isEmptyDocument
+{
+    NSString *result = [self.js executeJavaScript: Main_isEmptyDocument nargs: 0];
+    return [result isEqualToString: @"true"];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                           JSMetadata                                           //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation JSMetadata
+
+- (NSDictionary *)getMetadata
+{
+    return [self.js executeJavaScriptJSON: Metadata_getMetadata nargs: 0];
+}
+
+- (void)setMetadata:(NSDictionary *)metadata
+{
+    [self.js executeJavaScriptJSON: Metadata_setMetadata nargs: 1, metadata];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                            JSOutline                                           //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Functions implemented in Outline.js
+
+@implementation JSOutline
+
+- (NSDictionary *)getOutline
+{
+    return [self.js executeJavaScriptJSON: Outline_getOutline nargs: 0];
+}
+
+- (void)moveSection:(NSString *)sectionId parentId:(NSString *)parentId nextId:(NSString *)nextId
+{
+    [self.js executeJavaScript: Outline_moveSection nargs: 3, sectionId, parentId, nextId];
+}
+
+- (void)deleteItem:(NSString *)itemId
+{
+    [self.js executeJavaScript: Outline_deleteItem nargs: 1, itemId];
+}
+
+- (void)scheduleUpdateStructure
+{
+    [self.js executeJavaScript: Outline_scheduleUpdateStructure nargs: 0];
+}
+
+- (void)goToItem:(NSString *)itemId
+{
+    [self.js executeJavaScript: Outline_goToItem nargs: 1, itemId];
+}
+
+- (void)set:(NSString *)itemId numbered:(BOOL)numbered
+{
+    [self.js executeJavaScript: Outline_setNumbered nargs: 2, itemId, [NSNumber numberWithBool: numbered]];
+}
+
+- (void)set:(NSString *)itemId title:(NSString *)title
+{
+    [self.js executeJavaScript: Outline_setTitle nargs: 2, itemId, title];
+}
+
+- (void)insertTableOfContents
+{
+    [self.js executeJavaScript: Outline_insertTableOfContents nargs: 0];
+}
+
+- (void)insertListOfFigures
+{
+    [self.js executeJavaScript: Outline_insertListOfFigures nargs: 0];
+}
+
+- (void)insertListOfTables
+{
+    [self.js executeJavaScript: Outline_insertListOfTables nargs: 0];
+}
+
+- (void)setPrintMode:(BOOL)printMode
+{
+    [self.js executeJavaScript: Outline_setPrintMode nargs: 1, [NSNumber numberWithBool: printMode]];
+}
+
+- (NSDictionary *)examinePrintLayout:(int)pageHeight
+{
+    return [self.js executeJavaScriptJSON: Outline_examinePrintLayout nargs: 1,
+            [NSNumber numberWithInt: pageHeight]];
+}
+
+- (BOOL)detectSectionNumbering
+{
+    NSString *result = [self.js executeJavaScript: Outline_detectSectionNumbering nargs: 0];
+    return [result isEqualToString: @"true"];
+}
+
+- (NSDictionary *)findUsedStyles
+{
+    return [self.js executeJavaScriptJSON: Outline_findUsedStyles nargs: 0];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                            JSPreview                                           //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation JSPreview
+
+- (void)showForStyle:(NSString *)styleId uiName:(NSString *)uiName title:(NSString *)title
+{
+    [self.js executeJavaScript: Preview_showForStyle nargs: 3, styleId, uiName, title];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                             JSScan                                             //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation JSScan
+
+- (void)reset
+{
+    [self.js executeJavaScript: Scan_reset nargs: 0];
+}
+
+- (EDScanParagraph *)next
+{
+    NSDictionary *dict = [self.js executeJavaScriptJSON: Scan_next nargs: 0];
+    if ((dict == nil) || ![dict isKindOfClass: [NSDictionary class]])
+        return nil;
+
+    NSString *text = FCGetString(dict,@"text",nil);
+    NSString *sectionId = FCGetString(dict,@"sectionId",nil);
+    if (text == nil)
+        return nil;
+
+    return [[EDScanParagraph alloc] initWithText: text sectionId: sectionId];
+}
+
+- (int)addMatchStart:(int)start end:(int)end
+{
+    return [self.js executeJavaScript: Scan_addMatch nargs: 2,
+            [NSNumber numberWithInt: start],
+            [NSNumber numberWithInt: end]].intValue;
+}
+
+- (void)showMatch:(int)matchId
+{
+    [self.js executeJavaScript: Scan_showMatch nargs: 1, [NSNumber numberWithInt: matchId]];
+}
+
+- (void)replaceMatch:(int)matchId with:(NSString *)text
+{
+    [self.js executeJavaScript: Scan_replaceMatch nargs: 2, [NSNumber numberWithInt: matchId], text];
+}
+
+- (void)removeMatch:(int)matchId
+{
+    [self.js executeJavaScript: Scan_removeMatch nargs: 1, [NSNumber numberWithInt: matchId]];
+}
+
+- (void)goToMatch:(int)matchId
+{
+    [self.js executeJavaScript: Scan_goToMatch nargs: 1, [NSNumber numberWithInt: matchId]];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                           JSSelection                                          //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Functions implemented in Selection.js
+
+@implementation JSSelection
+
+- (void)update
+{
+    [self.js executeJavaScript: Selection_update nargs: 0];
+}
+
+- (void)selectAll
+{
+    [self.js executeJavaScript: Selection_selectAll nargs: 0];
+}
+
+- (void)selectParagraph
+{
+    [self.js executeJavaScript: Selection_selectParagraph nargs: 0];
+}
+
+- (void)selectWordAtCursor
+{
+    [self.js executeJavaScript: Selection_selectWordAtCursor nargs: 0];
+}
+
+- (NSString *)dragSelectionBeginX:(int)x y:(int)y selectWord:(BOOL)selectWord
+{
+    return [self.js executeJavaScript: Selection_dragSelectionBegin nargs: 3,
+            [NSNumber numberWithInt: x], [NSNumber numberWithInt: y],
+            [NSNumber numberWithBool: selectWord]];
+}
+
+- (NSString *)dragSelectionUpdateX:(int)x y:(int)y selectWord:(BOOL)selectWord
+{
+    return [self.js executeJavaScript: Selection_dragSelectionUpdate nargs: 3,
+            [NSNumber numberWithInt: x], [NSNumber numberWithInt: y],
+            [NSNumber numberWithBool: selectWord]];
+}
+
+- (NSString *)moveStartLeft
+{
+    return [self.js executeJavaScript: Selection_moveStartLeft nargs: 0];
+}
+
+- (NSString *)moveStartRight
+{
+    return [self.js executeJavaScript: Selection_moveStartRight nargs: 0];
+}
+
+- (NSString *)moveEndLeft
+{
+    return [self.js executeJavaScript: Selection_moveEndLeft nargs: 0];
+}
+
+- (NSString *)moveEndRight
+{
+    return [self.js executeJavaScript: Selection_moveEndRight nargs: 0];
+}
+
+- (void)setSelectionStartAtCoordsX:(int)x y:(int)y
+{
+    [self.js executeJavaScript: Selection_setSelectionStartAtCoords nargs: 2,
+                             [NSNumber numberWithInt: x], [NSNumber numberWithInt: y]];
+}
+
+- (void)setSelectionEndAtCoordsX:(int)x y:(int)y
+{
+    [self.js executeJavaScript: Selection_setSelectionEndAtCoords nargs: 2,
+                             [NSNumber numberWithInt: x], [NSNumber numberWithInt: y]];
+}
+
+- (void)setTableSelectionEdge:(NSString *)edge atCoordsX:(int)x y:(int)y
+{
+    [self.js executeJavaScript: Selection_setTableSelectionEdgeAtCoords nargs: 3,
+                           edge, [NSNumber numberWithInt: x], [NSNumber numberWithInt: y]];
+}
+
+- (void)print
+{
+    [self.js executeJavaScript: Selection_print nargs: 0];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                            JSStyles                                            //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation JSStyles
+
+- (NSString *)getCSSText
+{
+    return [self.js executeJavaScript: Styles_getCSSText nargs: 0];
+}
+
+- (void)setCSSText:(NSString *)cssText rules:(NSDictionary *)rules
+{
+//    debug(@"---------------------- setCSSText ----------------------\n");
+//    debug(@"%@",cssText);
+//    if ((cssText.length > 0) && ([cssText characterAtIndex: cssText.length-1] != '\n'))
+//        debug(@"\n");
+//    debug(@"--------------------------------------------------------\n");
+    [self.js executeJavaScriptJSON: Styles_setCSSText nargs: 2, cssText, rules];
+}
+
+- (NSString *)paragraphClass
+{
+    return [self.js executeJavaScript: Styles_getParagraphClass nargs: 0];
+}
+
+- (void)setParagraphClass:(NSString *)paragraphClass;
+{
+    [self.js executeJavaScript: Styles_setParagraphClass nargs: 1, paragraphClass];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                            JSTables                                            //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Functions implemented in Tables.js
+
+@implementation JSTables
+
+- (void)insertTableRows:(int)rows cols:(int)cols width:(NSString *)width numbered:(BOOL)numbered
+                caption:(NSString *)caption className:(NSString *)className;
+{
+    [self.js executeJavaScript: Tables_insertTable nargs: 6,
+                             [NSNumber numberWithInt: rows], [NSNumber numberWithInt: cols],
+                             width, [NSNumber numberWithBool: numbered], caption, className];
+}
+
+- (void)addAdjacentRow
+{
+    [self.js executeJavaScript: Tables_addAdjacentRow nargs: 0];
+}
+
+- (void)addAdjacentColumn
+{
+    [self.js executeJavaScript: Tables_addAdjacentColumn nargs: 0];
+}
+
+- (void)removeAdjacentRow
+{
+    [self.js executeJavaScript: Tables_removeAdjacentRow nargs: 0];
+}
+
+- (void)removeAdjacentColumn
+{
+    [self.js executeJavaScript: Tables_removeAdjacentColumn nargs: 0];
+}
+
+- (void)clearCells
+{
+    [self.js executeJavaScript: Tables_clearCells nargs: 0];
+}
+
+- (void)mergeCells
+{
+    [self.js executeJavaScript: Tables_mergeCells nargs: 0];
+}
+
+- (void)splitSelection
+{
+    [self.js executeJavaScript: Tables_splitSelection nargs: 0];
+}
+
+- (NSString *)getSelectedTableId
+{
+    return [self.js executeJavaScript: Tables_getSelectedTableId nargs: 0];
+}
+
+- (NSDictionary *)getProperties:(NSString *)itemId
+{
+    return [self.js executeJavaScriptJSON: Tables_getProperties nargs: 1, itemId];
+}
+
+- (void)setProperties:(NSString *)itemId width:(NSString *)width
+{
+    [self.js executeJavaScript: Tables_setProperties nargs: 2, itemId, width];
+}
+
+- (void)set:(NSString *)itemId colWidths:(NSArray *)colWidths
+{
+    [self.js executeJavaScript: Tables_setColWidths nargs: 2, itemId, colWidths];
+}
+
+- (NSDictionary *)getGeometry:(NSString *)itemId
+{
+    return [self.js executeJavaScriptJSON: Tables_getGeometry nargs: 1, itemId];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                          JSUndoManager                                         //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation JSUndoManager
+
+- (int)getLength
+{
+    NSString *result = [self.js executeJavaScript: UndoManager_getLength nargs: 0];
+    return result.intValue;
+}
+
+- (int)getIndex
+{
+    NSString *result = [self.js executeJavaScript: UndoManager_getIndex nargs: 0];
+    return result.intValue;
+}
+
+- (void)setIndex:(int)index
+{
+    [self.js executeJavaScript: UndoManager_setIndex nargs: 1, [NSNumber numberWithInt: index]];
+}
+
+- (void)undo
+{
+    [self.js executeJavaScript: UndoManager_undo nargs: 0];
+}
+
+- (void)redo
+{
+    [self.js executeJavaScript: UndoManager_redo nargs: 0];
+}
+
+- (void)newGroup:(NSString *)name
+{
+    [self.js executeJavaScript: UndoManager_newGroup nargs: 1, name];
+}
+
+- (NSString *)groupType
+{
+    return [self.js executeJavaScript: UndoManager_groupType nargs: 0];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                           JSViewport                                           //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Functions implemented in Viewport.js
+
+@implementation JSViewport
+
+- (void)setViewportWidth:(int)width
+{
+    [self.js executeJavaScript: Viewport_setViewportWidth nargs: 1, [NSNumber numberWithInt: width]];
+}
+
+- (void)setTextScale:(int)textScale
+{
+    [self.js executeJavaScript: Viewport_setTextScale nargs: 1, [NSNumber numberWithInt: textScale]];
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDObservation.h
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDObservation.h b/experiments/objcFramework/EDObservation.h
new file mode 100644
index 0000000..9e69301
--- /dev/null
+++ b/experiments/objcFramework/EDObservation.h
@@ -0,0 +1,30 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import <Foundation/Foundation.h>
+
+void addObserverForAllProperties(NSObject *target, NSObject *anObserver,
+                                 NSKeyValueObservingOptions options, void *context);
+
+void removeObserverForAllProperties(NSObject *target, NSObject *anObserver);

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDObservation.m
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDObservation.m b/experiments/objcFramework/EDObservation.m
new file mode 100644
index 0000000..8c187c1
--- /dev/null
+++ b/experiments/objcFramework/EDObservation.m
@@ -0,0 +1,56 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import "EDObservation.h"
+#import <objc/runtime.h>
+
+static NSArray *getPropertyNames(Class cls)
+{
+    NSMutableArray *propertyNames = [NSMutableArray arrayWithCapacity: 0];
+    while ((cls != nil) && (cls != [NSObject class])) {
+
+        unsigned int propertyCount;
+        objc_property_t *properties = class_copyPropertyList(cls,&propertyCount);
+        for (unsigned int i = 0; i < propertyCount; i++) {
+            const char *name = property_getName(properties[i]);
+            [propertyNames addObject: [NSString stringWithCString: name encoding: NSUTF8StringEncoding]];
+        }
+        free(properties);
+        cls = class_getSuperclass(cls);
+    }
+    return propertyNames;
+}
+
+void addObserverForAllProperties(NSObject *target, NSObject *anObserver,
+                                 NSKeyValueObservingOptions options, void *context)
+{
+    for (NSString *property in getPropertyNames(target.class))
+        [target addObserver: anObserver forKeyPath: property options: options context: nil];
+}
+
+void removeObserverForAllProperties(NSObject *target, NSObject *anObserver)
+{
+    for (NSString *property in getPropertyNames(target.class))
+        [target removeObserver: anObserver forKeyPath: property];
+}

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDOutline.h
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDOutline.h b/experiments/objcFramework/EDOutline.h
new file mode 100644
index 0000000..7bd4bf4
--- /dev/null
+++ b/experiments/objcFramework/EDOutline.h
@@ -0,0 +1,132 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import <Foundation/Foundation.h>
+
+#define L10NOutlineTitle                             NSLocalizedString(@"OutlineTitle",nil)
+#define L10NOutlineGrpSections                       NSLocalizedString(@"OutlineGrpSections",nil)
+#define L10NOutlineGrpFigures                        NSLocalizedString(@"OutlineGrpFigures",nil)
+#define L10NOutlineGrpTables                         NSLocalizedString(@"OutlineGrpTables",nil)
+#define L10NOutlineBtnAddNumber                      NSLocalizedString(@"OutlineBtnAddNumber",nil)
+#define L10NOutlineBtnRemoveNumber                   NSLocalizedString(@"OutlineBtnRemoveNumber",nil)
+#define L10NOutlineFigureCaptionPrefix               NSLocalizedString(@"OutlineFigureCaptionPrefix",nil)
+#define L10NOutlineTableCaptionPrefix                NSLocalizedString(@"OutlineTableCaptionPrefix",nil)
+#define L10NOutlineDelSectionTitle                   NSLocalizedString(@"OutlineDelSectionTitle",nil)
+#define L10NOutlineDelSectionHaveSub                 NSLocalizedString(@"OutlineDelSectionHaveSub",nil)
+#define L10NOutlineDelSectionNoSub                   NSLocalizedString(@"OutlineDelSectionNoSub",nil)
+#define L10NOutlineNone                              NSLocalizedString(@"OutlineNone",nil)
+
+@class EDOutlineItem;
+@class EDOutlineCategory;
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                        EDOutlineDelegate                                       //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@protocol EDOutlineDelegate
+
+- (void)outlineMoveItem:(EDOutlineItem *)item parent:(EDOutlineItem *)parent next:(EDOutlineItem *)next;
+- (void)outlineDeleteItem:(NSString *)itemId;
+- (void)outlineChanged;
+- (void)outlineItemSelected:(NSString *)itemId;
+- (void)outlineItemExpanded:(EDOutlineItem *)item category:(EDOutlineCategory *)category;
+- (void)outlineItemCollapsed:(EDOutlineItem *)item category:(EDOutlineCategory *)category;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                          EDOutlineItem                                         //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+typedef enum {
+    OutlineItemTypeSection,
+    OutlineItemTypeFigure,
+    OutlineItemTypeTable
+} OutlineItemType;
+
+@interface EDOutlineItem : NSObject
+
+@property (copy, readonly) NSString *itemId;
+@property (assign, readonly) OutlineItemType type;
+@property (assign, nonatomic) BOOL expanded;
+@property (weak, nonatomic) EDOutlineItem *parent;
+@property (copy, nonatomic) NSString *title;
+@property (copy, nonatomic) NSString *number;
+@property (strong, nonatomic) NSArray *children;
+
+@property (strong, readonly) NSString *description;
+@property (strong, readonly) NSString *detail;
+
+- (EDOutlineItem *)initWithItemId:(NSString *)itemId type:(OutlineItemType)type;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                        EDOutlineCategory                                       //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface EDOutlineCategory : NSObject
+
+@property (copy, readonly) NSString *title;
+@property (assign, readonly) int sectionIndex;
+@property (strong, nonatomic) NSArray *rootItems;
+@property (strong, readonly) NSArray *expandedItems;
+
+- (EDOutlineCategory *)initWithTitle:(NSString *)title sectionIndex:(int)sectionIndex;
+- (void)updateExpandedItems;
+- (NSArray *)indexPathsForExpandedDescendants:(EDOutlineItem *)item;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                            EDOutline                                           //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface EDOutline : NSObject <NSCopying>
+
+@property (strong, readonly) EDOutlineCategory *sections;
+@property (strong, readonly) EDOutlineCategory *figures;
+@property (strong, readonly) EDOutlineCategory *tables;
+@property (strong, readonly) NSArray *categories;
+@property (weak) id<EDOutlineDelegate> delegate;
+@property (strong, readonly) NSDictionary *itemsById;
+@property (copy, nonatomic) NSDictionary *json;
+
+- (EDOutline *)init;
+- (void)addItem:(NSString *)itemId type:(NSString *)type title:(NSString *)title;
+- (void)updateItem:(NSString *)itemId title:(NSString *)title;
+- (void)removeItem:(NSString *)itemId;
+- (EDOutlineItem *)lookupItem:(NSString *)itemId;
+- (void)expandItem:(EDOutlineItem *)item category:(EDOutlineCategory *)category;
+- (void)collapseItem:(EDOutlineItem *)item category:(EDOutlineCategory *)category;
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDOutline.m
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDOutline.m b/experiments/objcFramework/EDOutline.m
new file mode 100644
index 0000000..7ac45e4
--- /dev/null
+++ b/experiments/objcFramework/EDOutline.m
@@ -0,0 +1,270 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import "EDOutline.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                          EDOutlineItem                                         //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation EDOutlineItem
+
+- (EDOutlineItem *)initWithItemId:(NSString *)itemId type:(OutlineItemType)type
+{
+    if (!(self = [super init]))
+        return nil;
+
+    _itemId = [itemId copy];
+    _type = type;
+
+    return self;
+}
+
+- (NSString *)description
+{
+    switch (_type) {
+        case OutlineItemTypeSection: {
+            if (_number.length > 0)
+                return [NSString stringWithFormat: @"%@ %@", _number, _title];
+            else
+                return _title;
+        }
+        case OutlineItemTypeFigure:
+            return [NSString stringWithFormat: L10NOutlineFigureCaptionPrefix, _number];
+        case OutlineItemTypeTable:
+            return [NSString stringWithFormat: L10NOutlineTableCaptionPrefix, _number];
+    }
+    return nil;
+}
+
+- (NSString *)detail
+{
+    switch (_type) {
+        case OutlineItemTypeSection:
+            return nil;
+        case OutlineItemTypeFigure:
+            return _title;
+        case OutlineItemTypeTable:
+            return _title;
+    }
+    return nil;
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                        EDOutlineCategory                                       //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation EDOutlineCategory
+{
+    NSMutableArray *_expandedItems;
+}
+
+- (EDOutlineCategory *)initWithTitle:(NSString *)title sectionIndex:(int)sectionIndex
+{
+    if (!(self = [super init]))
+        return nil;
+    _title = [title copy];
+    _sectionIndex = sectionIndex;
+    _rootItems = [NSMutableArray arrayWithCapacity: 0];
+    _expandedItems = [NSMutableArray arrayWithCapacity: 0];
+    return self;
+}
+
+- (void)buildExpandedItems:(EDOutlineItem *)item
+{
+    [_expandedItems addObject: item];
+    if (item.expanded) {
+        for (EDOutlineItem *child in item.children)
+            [self buildExpandedItems: child];
+    }
+}
+
+- (void)updateExpandedItems
+{
+    _expandedItems = [NSMutableArray arrayWithCapacity: 0];
+    for (EDOutlineItem *item in _rootItems)
+        [self buildExpandedItems: item];
+}
+
+- (void)setRootItems:(NSArray *)newRootItems
+{
+    _rootItems = newRootItems;
+    [self updateExpandedItems];
+}
+
+- (NSUInteger)countExpandedDescendants:(EDOutlineItem *)parent
+{
+    NSUInteger result = 0;
+    for (EDOutlineItem *child in parent.children) {
+        result++;
+        if (child.expanded)
+            result += [self countExpandedDescendants: child];
+    }
+    return result;
+}
+
+- (NSArray *)indexPathsForExpandedDescendants:(EDOutlineItem *)item
+{
+    NSUInteger extra = [self countExpandedDescendants: item];
+    NSUInteger itemIndex = [_expandedItems indexOfObject: item];
+    assert(itemIndex < _expandedItems.count);
+    NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity: extra];
+    for (NSUInteger i = 0; i < extra; i++) {
+        NSUInteger indexes[2];
+        indexes[0] = _sectionIndex;
+        indexes[1] = itemIndex + i + 1;
+        [indexPaths addObject: [NSIndexPath indexPathWithIndexes: indexes length: 2]];
+    }
+    return indexPaths;
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                            EDOutline                                           //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation EDOutline
+{
+    NSMutableDictionary *_itemsById;
+}
+
+- (EDOutline *)init
+{
+    if (!(self = [super init]))
+        return nil;
+    _sections = [[EDOutlineCategory alloc] initWithTitle: L10NOutlineGrpSections sectionIndex: 0];
+    _figures = [[EDOutlineCategory alloc] initWithTitle: L10NOutlineGrpFigures sectionIndex: 1];
+    _tables = [[EDOutlineCategory alloc] initWithTitle: L10NOutlineGrpTables sectionIndex: 2];
+    _categories = [NSArray arrayWithObjects: _sections, _figures, _tables, nil];
+    _itemsById = [NSMutableDictionary dictionaryWithCapacity: 0];
+    return self;
+}
+
+- (id)copyWithZone:(NSZone *)zone
+{
+    EDOutline *copy = [[EDOutline allocWithZone: zone] init];
+    for (EDOutlineItem *item in _itemsById.allValues) {
+        EDOutlineItem *itemCopy = [[EDOutlineItem alloc] initWithItemId: item.itemId type: item.type];
+        itemCopy.title = item.title;
+        [copy->_itemsById setObject: itemCopy forKey: itemCopy.itemId];
+    }
+    copy.json = _json;
+    return copy;
+}
+
+- (NSArray *)addItems:(NSArray *)items parent:(EDOutlineItem *)parent type:(OutlineItemType)type
+{
+    NSMutableArray *result = [NSMutableArray arrayWithCapacity: 0];
+
+    for (NSDictionary *dict in items) {
+        NSString *itemId = [dict objectForKey: @"id"];
+        NSString *number = [dict objectForKey: @"number"];
+        NSArray *children = [dict objectForKey: @"children"];
+
+        EDOutlineItem *item = [_itemsById objectForKey: itemId];
+        assert(item != nil);
+        item.number = number;
+        item.parent = parent;
+        item.children = [self addItems: children parent: item type: type];
+        [result addObject: item];
+    }
+
+    return result;
+}
+
+- (void)setJson:(NSDictionary *)newJson
+{
+    _json = [newJson copy];
+
+    NSArray *jsonSections = [_json objectForKey: @"sections"];
+    NSArray *jsonFigures = [_json objectForKey: @"figures"];
+    NSArray *jsonTables = [_json objectForKey: @"tables"];
+
+    // Add/update items from jsonOutline
+    _sections.rootItems = [self addItems: jsonSections parent: nil type: OutlineItemTypeSection];
+    _figures.rootItems = [self addItems: jsonFigures parent: nil type: OutlineItemTypeFigure];
+    _tables.rootItems = [self addItems: jsonTables parent: nil type: OutlineItemTypeTable];
+}
+
+- (void)addItem:(NSString *)itemId type:(NSString *)type title:(NSString *)title
+{
+    assert([_itemsById objectForKey: itemId] == nil);
+    OutlineItemType itemType;
+    if ([type isEqualToString: @"figure"])
+        itemType = OutlineItemTypeFigure;
+    else if ([type isEqualToString: @"table"])
+        itemType = OutlineItemTypeTable;
+    else
+        itemType = OutlineItemTypeSection;
+    EDOutlineItem *item = [[EDOutlineItem alloc] initWithItemId: itemId type: itemType];
+    item.title = title;
+    [_itemsById setObject: item forKey: itemId];
+}
+
+- (void)updateItem:(NSString *)itemId title:(NSString *)title
+{
+    assert([_itemsById objectForKey: itemId] != nil);
+    EDOutlineItem *item = [_itemsById objectForKey: itemId];
+    item.title = title;
+}
+
+- (void)removeItem:(NSString *)itemId
+{
+    assert([_itemsById objectForKey: itemId] != nil);
+    [_itemsById removeObjectForKey: itemId];
+}
+
+- (EDOutlineItem *)lookupItem:(NSString *)itemId
+{
+    return [_itemsById objectForKey: itemId];
+}
+
+- (void)expandItem:(EDOutlineItem *)item category:(EDOutlineCategory *)category
+{
+    if (!item.expanded) {
+        item.expanded = YES;
+        [category updateExpandedItems];
+        [_delegate outlineItemExpanded: item category: category];
+    }
+}
+
+- (void)collapseItem:(EDOutlineItem *)item category:(EDOutlineCategory *)category
+{
+    if (item.expanded) {
+        item.expanded = NO;
+        [category updateExpandedItems];
+        [_delegate outlineItemCollapsed: item category: category];
+    }
+}
+
+@end



[4/4] incubator-corinthia git commit: Add Objective C framework code from UX Write

Posted by pm...@apache.org.
Add Objective C framework code from UX Write

These files come from the portion of the UX Write editor that works on
both Apple platforms (that is, it can run on either OS X or iOS). It's
in the repository for illustrative purposes only, to assist with the
creation of the framework for the Corinthia editor UI. The code does not
compile independently in its present form.


Project: http://git-wip-us.apache.org/repos/asf/incubator-corinthia/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-corinthia/commit/9f851a34
Tree: http://git-wip-us.apache.org/repos/asf/incubator-corinthia/tree/9f851a34
Diff: http://git-wip-us.apache.org/repos/asf/incubator-corinthia/diff/9f851a34

Branch: refs/heads/master
Commit: 9f851a34268fc20fe708626dff9795f4526edee9
Parents: 5202fbf
Author: Peter Kelly <pe...@uxproductivity.com>
Authored: Wed Aug 19 22:37:11 2015 +0700
Committer: Peter Kelly <pe...@uxproductivity.com>
Committed: Wed Aug 19 22:37:11 2015 +0700

----------------------------------------------------------------------
 experiments/objcFramework/DocFormats.h          |   79 +
 experiments/objcFramework/EDDocumentSetup.h     |   32 +
 experiments/objcFramework/EDDocumentSetup.m     |  225 +++
 experiments/objcFramework/EDEditor.h            |  112 ++
 experiments/objcFramework/EDEditor.m            |  413 ++++
 experiments/objcFramework/EDFileFormat.h        |   65 +
 experiments/objcFramework/EDFileFormat.m        |  968 ++++++++++
 experiments/objcFramework/EDFindReplace.h       |   48 +
 experiments/objcFramework/EDFindReplace.m       |   82 +
 experiments/objcFramework/EDGeometry.h          |   80 +
 experiments/objcFramework/EDGeometry.m          |  119 ++
 experiments/objcFramework/EDHTMLTidy.h          |   42 +
 experiments/objcFramework/EDHTMLTidy.m          |   58 +
 experiments/objcFramework/EDJSInterface.h       |  365 ++++
 experiments/objcFramework/EDJSInterface.m       | 1775 ++++++++++++++++++
 experiments/objcFramework/EDObservation.h       |   30 +
 experiments/objcFramework/EDObservation.m       |   56 +
 experiments/objcFramework/EDOutline.h           |  132 ++
 experiments/objcFramework/EDOutline.m           |  270 +++
 experiments/objcFramework/EDSaveOperation.h     |   44 +
 experiments/objcFramework/EDSaveOperation.m     |  166 ++
 experiments/objcFramework/EDScan.h              |   64 +
 experiments/objcFramework/EDScan.m              |  138 ++
 experiments/objcFramework/EDScanResults.h       |   73 +
 experiments/objcFramework/EDScanResults.m       |  109 ++
 .../objcFramework/EDSelectionFormatting.h       |   55 +
 .../objcFramework/EDSelectionFormatting.m       |   73 +
 experiments/objcFramework/EDStyle.h             |   59 +
 experiments/objcFramework/EDStyle.m             |  150 ++
 experiments/objcFramework/EDTiming.h            |   80 +
 experiments/objcFramework/EDTiming.m            |  271 +++
 experiments/objcFramework/EDUtil.h              |   41 +
 experiments/objcFramework/EDUtil.m              |  325 ++++
 experiments/objcFramework/EDWordCount.h         |   42 +
 experiments/objcFramework/EDWordCount.m         |   57 +
 experiments/objcFramework/Editor.h              |   40 +
 36 files changed, 6738 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/DocFormats.h
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/DocFormats.h b/experiments/objcFramework/DocFormats.h
new file mode 100644
index 0000000..ebfc49c
--- /dev/null
+++ b/experiments/objcFramework/DocFormats.h
@@ -0,0 +1,79 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+/** \mainpage
+
+ Documentation for the DocFormats library
+
+ # CSS
+
+ \see CSSProperties.h
+ \see CSSSheet.h
+ \see CSSStyle.h
+
+ # XML
+
+ \see DFDOM.h
+
+ # Word
+
+ \see WordPPr.h
+
+ */
+
+#import <Foundation/Foundation.h>
+
+#import "DFPlatform.h"
+#import <DocFormats/DocFormats.h>
+
+//#import <DocFormats/DFXMLNames.h>
+#import "DFXMLNames.h"
+//#import <DocFormats/DFXMLNamespaces.h>
+#import "DFXMLNamespaces.h"
+
+//#import <DocFormats/DFDOM.h>
+//#import <DocFormats/DFXML.h>
+#import "DFDOM.h"
+#import "DFXML.h"
+#import <DocFormats/DFXMLForward.h>
+
+#import "CSS.h"
+#import "CSSClassNames.h"
+#import "CSSLength.h"
+#import "CSSProperties.h"
+#import "CSSSelector.h"
+#import "CSSStyle.h"
+#import "CSSSheet.h"
+
+#import "DFHTML.h"
+#import "DFHTMLNormalization.h"
+
+#import "DFString.h"
+//#import "DFHashTable.h"
+#import "DFCallback.h"
+#import "DFFilesystem.h"
+#import <DocFormats/DFError.h>
+#import "DFZipFile.h"
+#import "Word.h"
+#import "HTMLToLaTeX.h"

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDDocumentSetup.h
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDDocumentSetup.h b/experiments/objcFramework/EDDocumentSetup.h
new file mode 100644
index 0000000..e05c2bf
--- /dev/null
+++ b/experiments/objcFramework/EDDocumentSetup.h
@@ -0,0 +1,32 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import <Foundation/Foundation.h>
+#import <Editor/DocFormats.h>
+
+void CSSSheetUseCSSNumbering(CSSSheet *sheet);
+CSSSheet *CSSSheetCreateDefault(void);
+DFDocument *DFHTMLCreateDefault(CSSSheet *styleSheet);
+int DFHTMLCreateDefaultFile(const char *filename, DFError **error);
+int DFWordCreateDefault(const char *filename, DFError **error);

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDDocumentSetup.m
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDDocumentSetup.m b/experiments/objcFramework/EDDocumentSetup.m
new file mode 100644
index 0000000..1ee2283
--- /dev/null
+++ b/experiments/objcFramework/EDDocumentSetup.m
@@ -0,0 +1,225 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import "EDDocumentSetup.h"
+#import "EDUtil.h"
+#import <FileClient/FileClient.h>
+
+#define L10NDocStringPreface                         NSLocalizedString(@"DocStringPreface",NULL)
+#define L10NDocStringRef                             NSLocalizedString(@"DocStringRef",NULL)
+#define L10NDocStringAbstract                        NSLocalizedString(@"DocStringAbstract",NULL)
+#define L10NDocStringBib                             NSLocalizedString(@"DocStringBib",NULL)
+#define L10NDocStringChapter                         NSLocalizedString(@"DocStringChapter",NULL)
+#define L10NDocStringAppendix                        NSLocalizedString(@"DocStringAppendix",NULL)
+#define L10NDocStringContents                        NSLocalizedString(@"DocStringContents",NULL)
+#define L10NDocStringListFigure                      NSLocalizedString(@"DocStringListFigure",NULL)
+#define L10NDocStringListTable                       NSLocalizedString(@"DocStringListTable",NULL)
+#define L10NDocStringIndex                           NSLocalizedString(@"DocStringIndex",NULL)
+#define L10NDocStringFigure                          NSLocalizedString(@"DocStringFigure",NULL)
+#define L10NDocStringTable                           NSLocalizedString(@"DocStringTable",NULL)
+#define L10NDocStringPart                            NSLocalizedString(@"DocStringPart",NULL)
+#define L10NDocStringEncl                            NSLocalizedString(@"DocStringEncl",NULL)
+#define L10NDocStringCc                              NSLocalizedString(@"DocStringCc",NULL)
+#define L10NDocStringHeadto                          NSLocalizedString(@"DocStringHeadto",NULL)
+#define L10NDocStringPage                            NSLocalizedString(@"DocStringPage",NULL)
+#define L10NDocStringSee                             NSLocalizedString(@"DocStringSee",NULL)
+#define L10NDocStringAlso                            NSLocalizedString(@"DocStringAlso",NULL)
+#define L10NDocStringProof                           NSLocalizedString(@"DocStringProof",NULL)
+#define L10NDocStringGlossary                        NSLocalizedString(@"DocStringGlossary",NULL)
+
+// FIXME: This won't work now for Word documents, where styles always have class names
+// FIXME: Not covered by tests
+void CSSSheetUseCSSNumbering(CSSSheet *sheet)
+{
+    CSSStyle *style;
+
+    style = CSSSheetLookupElement(sheet,"body",NULL,1,0);
+    CSSPut(CSSStyleRule(style),"counter-reset","h1 h2 h3 h4 h5 h6 figure table");
+
+    // Figure caption
+    style = CSSSheetLookupElement(sheet,"figcaption",NULL,1,1);
+    CSSPut(CSSStyleRule(style),"counter-increment","figure");
+    NSString *content = [NSString stringWithFormat: @"\"%@ \" counter(figure) \": \"", L10NDocStringFigure];
+    CSSPut(CSSStyleBefore(style),"content",content.UTF8String);
+
+    style = CSSSheetLookupElement(sheet,"figcaption","Unnumbered",1,1);
+    CSSPut(CSSStyleRule(style),"counter-increment","figure 0");
+    CSSPut(CSSStyleBefore(style),"content","\"\"");
+
+    // Table caption
+    style = CSSSheetLookupElement(sheet,"caption",NULL,1,1);
+    CSSPut(CSSStyleRule(style),"caption-side","bottom");
+    CSSPut(CSSStyleRule(style),"counter-increment","table");
+    content = [NSString stringWithFormat: @"\"%@ \" counter(table) \": \"", L10NDocStringTable];
+    CSSPut(CSSStyleBefore(style),"content",content.UTF8String);
+
+    style = CSSSheetLookupElement(sheet,"caption","Unnumbered",1,1);
+    CSSPut(CSSStyleRule(style),"counter-increment","table 0");
+    CSSPut(CSSStyleBefore(style),"content","\"\"");
+
+    // Table of contents
+    style = CSSSheetLookupElement(sheet,"nav","tableofcontents",1,1);
+    if (CSSGet(CSSStyleBefore(style),"content") == NULL) {
+        content = [NSString stringWithFormat: @"\"%@\"", L10NDocStringContents];
+        CSSPut(CSSStyleBefore(style),"content",content.UTF8String);
+        CSSPut(CSSStyleBefore(style),"font-size","2em");
+        CSSSetBold(CSSStyleBefore(style),1);
+        CSSPut(CSSStyleBefore(style),"margin-top",".67em");
+        CSSPut(CSSStyleBefore(style),"margin-bottom",".67em");
+        CSSPut(CSSStyleBefore(style),"display","block");
+    }
+
+    // List of figures
+    style = CSSSheetLookupElement(sheet,"nav","listoffigures",1,1);
+    if (CSSGet(CSSStyleBefore(style),"content") == NULL) {
+        content = [NSString stringWithFormat: @"\"%@\"", L10NDocStringListFigure];
+        CSSPut(CSSStyleBefore(style),"content",content.UTF8String);
+        CSSPut(CSSStyleBefore(style),"font-size","2em");
+        CSSSetBold(CSSStyleBefore(style),1);
+        CSSPut(CSSStyleBefore(style),"margin-top",".67em");
+        CSSPut(CSSStyleBefore(style),"margin-bottom",".67em");
+        CSSPut(CSSStyleBefore(style),"display","block");
+    }
+
+    // List of tables
+    style = CSSSheetLookupElement(sheet,"nav","listoftables",1,1);
+    if (CSSGet(CSSStyleBefore(style),"content") == NULL) {
+        content = [NSString stringWithFormat: @"\"%@\"", L10NDocStringListTable];
+        CSSPut(CSSStyleBefore(style),"content",content.UTF8String);
+        CSSPut(CSSStyleBefore(style),"font-size","2em");
+        CSSSetBold(CSSStyleBefore(style),1);
+        CSSPut(CSSStyleBefore(style),"margin-top",".67em");
+        CSSPut(CSSStyleBefore(style),"margin-bottom",".67em");
+        CSSPut(CSSStyleBefore(style),"display","block");
+    }
+}
+
+CSSSheet *CSSSheetCreateDefault(void)
+{
+    CSSSheet *styleSheet = CSSSheetNew();
+    CSSSheetUseCSSNumbering(styleSheet);
+
+    CSSStyle *title = CSSSheetLookupElement(styleSheet,"p","Title",1,0);
+    CSSPut(CSSStyleRule(title),"font-size","24pt");
+    CSSPut(CSSStyleRule(title),"text-align","center");
+
+    CSSStyle *author = CSSSheetLookupElement(styleSheet,"p","Author",1,0);
+    CSSPut(CSSStyleRule(author),"font-size","18pt");
+    CSSPut(CSSStyleRule(author),"text-align","center");
+
+    CSSStyle *abstract = CSSSheetLookupElement(styleSheet,"p","Abstract",1,0);
+    CSSPut(CSSStyleRule(abstract),"font-style","italic");
+    CSSPut(CSSStyleRule(abstract),"margin-left","20%");
+    CSSPut(CSSStyleRule(abstract),"margin-right","20%");
+
+    CSSStyle *body = CSSSheetLookupElement(styleSheet,"body",NULL,1,0);
+    CSSPut(CSSStyleRule(body),"margin-left","10%");
+    CSSPut(CSSStyleRule(body),"margin-right","10%");
+    CSSPut(CSSStyleRule(body),"margin-top","10%");
+    CSSPut(CSSStyleRule(body),"margin-bottom","10%");
+    CSSPut(CSSStyleRule(body),"text-align","justify");
+
+    CSSStyle *page = CSSSheetLookupElement(styleSheet,"@page",NULL,1,0);
+    CSSPut(CSSStyleRule(page),"size","a4 portrait");
+    return styleSheet;
+}
+
+DFDocument *DFHTMLCreateDefault(CSSSheet *styleSheet)
+{
+    DFDocument *doc = DFDocumentNewWithRoot(HTML_HTML);
+    DFNode *head = DFCreateChildElement(doc->root,HTML_HEAD);
+    DFNode *body = DFCreateChildElement(doc->root,HTML_BODY);
+    DFNode *meta = DFCreateChildElement(head,HTML_META);
+    DFSetAttribute(meta,HTML_CHARSET,"utf-8");
+    DFNode *style = DFCreateChildElement(head,HTML_STYLE);
+    char *cssText = CSSSheetCopyCSSText(styleSheet);
+    DFCreateChildTextNode(style,cssText);
+    free(cssText);
+    DFNode *p = DFCreateChildElement(body,HTML_P);
+    DFCreateChildElement(p,HTML_BR);
+    return doc;
+}
+
+int DFHTMLCreateDefaultFile(const char *filename, DFError **error)
+{
+    CSSSheet *styleSheet = CSSSheetCreateDefault();
+    DFDocument *doc = DFHTMLCreateDefault(styleSheet);
+    int ok = DFSerializeXMLFile(doc,0,1,filename,error);
+    DFDocumentRelease(doc);
+    CSSSheetRelease(styleSheet);
+    return ok;
+}
+
+static void addDefaultStyles(CSSSheet *styleSheet)
+{
+    // Explicitly add default heading formatting properties
+    for (int i = 1; i <= 6; i++) {
+        char *elementName = DFFormatString("h%d",i);
+        CSSStyle *style = CSSSheetLookupElement(styleSheet,elementName,NULL,1,0);
+        CSSStyleAddDefaultHTMLProperties(style);
+        free(elementName);
+    }
+
+    // Word uses the same "Caption" style for both figures and tables. So we only need to set
+    // caption. Note that in HTML the alignment is (I think) implicit.
+    CSSStyle *caption = CSSSheetLookupElement(styleSheet,"caption",NULL,1,0);
+    CSSPut(CSSStyleRule(caption),"text-align","center");
+    caption->latent = 0;
+}
+
+int DFWordCreateDefault(const char *filename, DFError **error)
+{
+    int ok = 0;
+    CSSSheet *styleSheet = NULL;
+    DFDocument *htmlDoc = NULL;
+    DFStorage *abstractStorage = NULL;
+    DFConcreteDocument *concreteDoc = NULL;
+    DFAbstractDocument *abstractDoc = NULL;
+
+    styleSheet = CSSSheetCreateDefault();
+    addDefaultStyles(styleSheet);
+
+    concreteDoc = DFConcreteDocumentCreateFile(filename,error);
+    if (concreteDoc == NULL)
+        goto end;
+
+    abstractStorage = DFStorageNewMemory(DFFileFormatHTML);
+    abstractDoc = DFAbstractDocumentNew(abstractStorage);
+
+    htmlDoc = DFHTMLCreateDefault(styleSheet);
+    DFAbstractDocumentSetHTML(abstractDoc,htmlDoc);
+
+    if (!DFCreate(concreteDoc,abstractDoc,error))
+        goto end;
+
+    ok = 1;
+
+end:
+    CSSSheetRelease(styleSheet);
+    DFDocumentRelease(htmlDoc);
+    DFStorageRelease(abstractStorage);
+    DFConcreteDocumentRelease(concreteDoc);
+    DFAbstractDocumentRelease(abstractDoc);
+    return ok;
+}

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDEditor.h
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDEditor.h b/experiments/objcFramework/EDEditor.h
new file mode 100644
index 0000000..e3f53a7
--- /dev/null
+++ b/experiments/objcFramework/EDEditor.h
@@ -0,0 +1,112 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import <Foundation/Foundation.h>
+#import <Editor/DocFormats.h>
+#import "EDGeometry.h"
+#import "EDSaveOperation.h"
+
+@class EDFileFormat;
+@class JSInterface;
+@class EDOutline;
+@class EDSelectionFormatting;
+@class EDTimingInfo;
+@class EDSaveOperation;
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                        EDSystemDelegate                                        //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@protocol EDSystemDelegate
+
+- (void)runInBackground:(void (^)(void))block completion:(void (^)(void))completion;
+- (void)runCommandInBackground:(BOOL (^)(NSError **commandError))command
+                    completion:(void (^)(NSError *completionError))completion;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                        EDEditorDelegate                                        //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@protocol EDEditorDelegate
+
+@property (nonatomic) BOOL editorIsSaving;
+@property (nonatomic, readonly) NSString *editorPath;
+
+- (void)editorDidUpdateCSS;
+- (void)editorDidUpdateOutline;
+- (void)editorDidSaveFile;
+- (void)editorShowResizeHandles:(EDItemGeometry *)geometry vertical:(BOOL)vertical;
+- (void)editorHideResizeHandles;
+
+- (NSString *)saveResource:(NSData *)data prefix:(NSString *)prefix extension:(NSString *)extension
+                     error:(NSError **)error;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                            EDEditor                                            //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface EDEditor : NSObject <EDResizeDelegate>
+
+@property (weak) NSObject<EDEditorDelegate> *delegate;
+@property (weak) NSObject<EDSystemDelegate> *system;
+@property (strong, readonly) EDFileFormat *fileFormat;
+@property (strong, readonly) NSString *generator;
+@property (strong, readonly) NSString *tempDir;
+@property (strong, readwrite) JSInterface *js;
+@property (assign, readonly) BOOL jsInitOk;
+@property (strong, readonly) EDOutline *outline;
+@property (strong, readwrite, nonatomic) EDSelectionFormatting *formatting;
+@property (copy, nonatomic) NSString *paragraphStyleId;
+@property (assign, readonly) CSSSheet *styleSheet;
+@property (strong, readonly) EDTimingInfo *loadTiming;
+@property (strong, readonly) EDTimingInfo *saveTiming;
+@property (strong) EDSaveOperation *activeSave;
+@property (strong) EDSaveOperation *pendingSave;
+@property (strong, readonly) NSString *origGenerator;
+@property (copy, nonatomic) NSString *locale;
+
+- (EDEditor *)initWithFileFormat:(EDFileFormat *)fileFormat generator:(NSString *)generator tempDir:(NSString *)tempDir;
+- (void)updateFormatting;
+- (void)updateCSS;
+- (void)retrieveStyles;
+- (void)makeStyleNonLatent:(const char *)ident;
+- (void)setOutlineDirty;
+- (void)loadAndInitSucceeded;
+- (void)dumpHTML;
+- (void)saveTo:(NSString *)path completion:(EDSaveCompletion)completion;
+- (void)debugSaveStatus;
+- (void)undo;
+- (void)redo;
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDEditor.m
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDEditor.m b/experiments/objcFramework/EDEditor.m
new file mode 100644
index 0000000..d653952
--- /dev/null
+++ b/experiments/objcFramework/EDEditor.m
@@ -0,0 +1,413 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import "EDEditor.h"
+#import "EDOutline.h"
+#import "EDSelectionFormatting.h"
+#import "EDTiming.h"
+#import "EDObservation.h"
+#import "EDJSInterface.h"
+#import "EDSaveOperation.h"
+#import "EDFileFormat.h"
+#import "EDHTMLTidy.h"
+#import "EDUtil.h"
+#import "EDDocumentSetup.h"
+#import <FileClient/FCError.h>
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                        EDEditorDelegate                                        //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation EDEditor
+{
+    BOOL _outlineDirty;
+    int _programmaticChange;
+    BOOL _formattingDirty;
+    NSDictionary *_lastReceivedFormatting;
+}
+
+static void formattingPropertiesChanged(void *ctx, void *object, void *data)
+{
+    EDEditor *editor = (__bridge EDEditor *)ctx;
+    [editor detectedFormattingChange];
+}
+
+- (EDEditor *)initWithFileFormat:(EDFileFormat *)fileFormat generator:(NSString *)generator tempDir:(NSString *)tempDir
+{
+    if (!(self = [super init]))
+        return nil;
+
+    _fileFormat = fileFormat;
+    _fileFormat.editor = self;
+    _generator = [generator copy];
+    _tempDir = [tempDir copy];
+    _outline = [[EDOutline alloc] init];
+    _styleSheet = CSSSheetNew();
+    _loadTiming = [[EDTimingInfo alloc] init];
+    _saveTiming = [[EDTimingInfo alloc] init];
+
+    self.formatting = [[EDSelectionFormatting alloc] init];
+
+    return self;
+}
+
+- (void)dealloc
+{
+    self.formatting = nil;
+    CSSSheetRelease(_styleSheet);
+}
+
+- (void)setFormatting:(EDSelectionFormatting *)newFormatting
+{
+    if (_formatting != nil) {
+        removeObserverForAllProperties(_formatting,self);
+        DFCallbackRemove(&_formatting.cssProperties->changeCallbacks,formattingPropertiesChanged,(__bridge void *)self);
+    }
+
+    _formatting = newFormatting;
+
+    if (_formatting != nil) {
+        addObserverForAllProperties(_formatting,self,0,nil);
+        DFCallbackAdd(&_formatting.cssProperties->changeCallbacks,formattingPropertiesChanged,(__bridge void *)self);
+    }
+}
+
+- (void)setParagraphStyleId:(NSString *)paragraphStyleId
+{
+    _paragraphStyleId = [paragraphStyleId copy];
+    [self detectedFormattingChange];
+}
+
+- (void)setLocale:(NSString *)newLocale
+{
+    _locale = [newLocale copy];
+    [_js.main setLanguage: _locale];
+}
+
+- (void)updateResizeHandles
+{
+    BOOL vertical = NO;
+    EDItemGeometry *geometry = nil;
+    if (_formatting.inFigure) {
+        NSString *itemId = [_js.figures getSelectedFigureId];
+        if (itemId != nil) {
+            NSDictionary *dict = [_js.figures getGeometry: itemId];
+            if (dict != nil) {
+                geometry = [EDItemGeometry fromDict: dict];
+                vertical = YES;
+            }
+        }
+    }
+    else if (_formatting.inTable) {
+        NSString *itemId = [_js.tables getSelectedTableId];
+        if (itemId != nil) {
+            NSDictionary *dict = [_js.tables getGeometry: itemId];
+            if (dict != nil) {
+                geometry = [EDItemGeometry fromDict: dict];
+                vertical = NO;
+            }
+        }
+    }
+
+    if (geometry != nil)
+        [_delegate editorShowResizeHandles: geometry vertical: vertical];
+    else
+        [_delegate editorHideResizeHandles];
+}
+
+- (void)updateFormatting
+{
+    if (!_js.jsInitialised)
+        return;
+
+    _programmaticChange++;
+
+    NSMutableDictionary *modified = [[_js.formatting getFormatting] mutableCopy];
+    if (modified == nil)
+        modified = [NSMutableDictionary dictionaryWithCapacity: 0]; // in case of JS exception
+
+    NSString *newStyle = [modified objectForKey: @"-uxwrite-paragraph-style"];
+    [modified removeObjectForKey: @"-uxwrite-paragraph-style"];
+    if ((newStyle != nil) && (newStyle.length == 0))
+        newStyle = @"p";
+
+    _lastReceivedFormatting = modified;
+
+    // FIXME: Receive paragraph style id from javascript as an (element,class) tuple, not a string
+    self.paragraphStyleId = newStyle;
+
+    self.formatting = [[EDSelectionFormatting alloc] initWithProperties: modified];
+    [self updateResizeHandles];
+
+    _programmaticChange--;
+}
+
+- (void)applyFormattingChanges
+{
+    if (!_formattingDirty)
+        return;
+    _formattingDirty = false;
+
+    if (_js.jsInitialised) {
+        // If some properties that were previously set have been removed (e.g. font-weight in the
+        // case where bold has been turned off), set these as null entries in the dictionary we
+        // pass to applyFormattingChanges(). Otherwise, they won't be considered as properties
+        // that need changing, and will retain their previous values (e.g. font-weight will
+        // remain set if there's no explicit instruction to remove it).
+        DFHashTable *collapsed = CSSCollapseProperties(_formatting.cssProperties);
+        NSMutableDictionary *changes = [NSDictionaryFromHashTable(collapsed) mutableCopy];
+        DFHashTableRelease(collapsed);
+        if (_lastReceivedFormatting != nil) {
+            for (NSString *key in _lastReceivedFormatting) {
+                if ([changes objectForKey: key] == nil)
+                    [changes setObject: [NSNull null] forKey: key];
+            }
+        }
+        // FIXME: Send paragraph style id to javascript as an (element,class) tuple, not a string
+        [_js.formatting applyFormattingChangesStyle: _paragraphStyleId properties: changes];
+    }
+    [self updateFormatting];
+}
+
+- (void)detectedFormattingChange
+{
+    if (_programmaticChange == 0) {
+        _formattingDirty = YES;
+        // Invoke applyFormattingChanges after the current iteration of the event loop has finished.
+        // This way, if multiple properties of the formatting object are changed at the same time,
+        // we will only call the javascript applyFormatting() method once after all the changes
+        // have been made.
+        [self performSelector: @selector(applyFormattingChanges) withObject: nil afterDelay: 0];
+    }
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
+                        change:(NSDictionary *)change context:(void *)context
+{
+    if (object == _formatting)
+        [self detectedFormattingChange];
+}
+
+- (void)updateCSS
+{
+    [_delegate editorDidUpdateCSS];
+}
+
+- (void)retrieveStyles
+{
+    NSString *cssText = [_js.styles getCSSText];
+    CSSSheetUpdateFromCSSText(_styleSheet,cssText.UTF8String);
+    [_fileFormat setupInitialStyles];
+    CSSSheetUseCSSNumbering(_styleSheet);
+
+    // If any of the built-in styles (e.g. figcaption) are used in the document, but not explicitly
+    // mentioned in the CSS stylesheet text, then mark them as non-latent. This will cause their
+    // definitions to be explicitly included in the stylesheet text.
+    // FIXME: Get these sent from javascript as (element,class) pairs
+    NSDictionary *usedStyles = [_js.outline findUsedStyles];
+    if (_js.error == nil) {
+        for (NSString *rawSelector in usedStyles.allKeys) {
+            CSSStyle *style = CSSSheetLookupSelector(_styleSheet,rawSelector.UTF8String,NO,NO);
+            if ((style != nil) && (style->latent))
+                style->latent = NO;
+        }
+    }
+
+    // updateCSS causes the documentModified flag to be set, because normally it does actually
+    // represent an actual change to the document. But since this function is only called at load
+    // time, all we're actually doing here is making sure the HTML document's stylesheet is
+    // consistent with what we maintain on the Objective C side. So in this particular case we
+    // don't want it to count as a modification - otherwise simp,ly opening and closing the document
+    // would cause it to be saved, resulting in an unnecessary upload to the server
+    BOOL oldModified = _js.documentModified;
+    [self updateCSS];
+    _js.documentModified = oldModified;
+}
+
+- (void)makeStyleNonLatent:(const char *)ident
+{
+    // We must set the add parameter to YES here; otherwise, the latent parameter will be ignored
+    CSSSheetLookupSelector(_styleSheet,ident,YES,NO);
+    [self updateCSS];
+}
+
+- (void)updateOutline
+{
+    if (!_js.jsInitialised)
+        return;
+    _outlineDirty = NO;
+    _outline.json = [_js.outline getOutline];
+    [_delegate editorDidUpdateOutline];
+}
+
+- (void)setOutlineDirty
+{
+    if (!_outlineDirty) {
+        _outlineDirty = YES;
+        [self performSelector: @selector(updateOutline) withObject: nil afterDelay: 0];
+    }
+}
+
+- (void)loadAndInitSucceeded
+{
+    _jsInitOk = YES;
+    _js.jsInitialised = YES;
+    [_fileFormat finishLoad];
+    [_loadTiming addEntry: @"File format-specific initialisation"];
+    _origGenerator = [_js.main setGenerator: _generator];
+    [self retrieveStyles];
+    [self updateOutline];
+
+    NSString *localeOrEmpty = [_js.main getLanguage];
+    if (localeOrEmpty.length > 0)
+        _locale = localeOrEmpty;
+    else
+        _locale = nil;
+
+    [self updateFormatting];
+}
+
+- (void)dumpHTML
+{
+    NSString *inputStr = [_js.main getHTML];
+    NSData *input = [inputStr dataUsingEncoding: NSUTF8StringEncoding];
+    [EDHTMLTidy tidy: input isXHTML: NO editor: self completion:^(NSData *output, NSError *error) {
+        if (error != nil) {
+            debug(@"HTMLTidy failed: %@\n",FCErrorDescription(error));
+        }
+        else {
+            NSString *outputStr = [[NSString alloc] initWithData: output encoding: NSUTF8StringEncoding];
+            debug(@"\n");
+            debug(@"------------------------------------- HTML -------------------------------------\n");
+            debug(@"%@",outputStr);
+            debug(@"--------------------------------------------------------------------------------\n");
+            debug(@"\n");
+        }
+    }];
+}
+
+- (void)saveTo:(NSString *)path completion:(EDSaveCompletion)completion
+{
+    if (!_js.jsInitialised)
+        return;
+
+    if (_activeSave == nil) {
+        _activeSave = [[EDSaveOperation alloc] initWithEditor: self path: path];
+        [_activeSave addCompletion: completion];
+        [_activeSave start];
+    }
+    else {
+        if (_pendingSave == nil)
+            _pendingSave = [[EDSaveOperation alloc] initWithEditor: self path: path];
+        [_pendingSave addCompletion: completion];
+    }
+    [self debugSaveStatus];
+}
+
+- (void)debugSaveStatus
+{
+    NSMutableString *str = [NSMutableString stringWithCapacity: 0];
+    [str appendFormat: @"Save status:"];
+
+    if (_activeSave != nil)
+        [str appendFormat: @" activeSave = %p (%d)", _activeSave, (int)_activeSave.completionCount];
+    else
+        [str appendFormat: @" activeSave = nil"];
+
+    if (_pendingSave != nil)
+        [str appendFormat: @", pendingSave = %p (%d)", _pendingSave, (int)_pendingSave.completionCount];
+    else
+        [str appendFormat: @", pendingSave = nil"];
+
+    debug(@"%@\n",str);
+}
+
+- (void)undo
+{
+    if (self.js.jsInitialised) {
+        [self.js.undoManager undo];
+        [self updateFormatting];
+    }
+}
+
+- (void)redo
+{
+    if (self.js.jsInitialised) {
+        [self.js.undoManager redo];
+        [self updateFormatting];
+    }
+}
+
+// ResizeDelegate methods
+
+- (void)resizedWidthPct:(CGFloat)widthPct
+{
+    NSString *widthStr = [NSString stringWithFormat: @"%d%%", (int)round(widthPct)];
+
+    if (_formatting.inFigure) {
+        NSString *itemId = [_js.figures getSelectedFigureId];
+        if (itemId == nil)
+            return;
+
+        NSDictionary *properties = [_js.figures getProperties: itemId];
+        if (properties == nil)
+            return;
+
+        NSString *src = [properties objectForKey: @"src"];
+        [_js.figures setProperties: itemId width: widthStr src: src];
+
+    }
+    else if (_formatting.inTable) {
+        NSString *itemId = [_js.tables getSelectedTableId];
+        if (itemId == nil)
+            return;
+
+        [_js.tables setProperties: itemId width: widthStr];
+    }
+
+    [self updateFormatting];
+}
+
+- (void)resizedColumns:(NSArray *)widthPcts
+{
+    if (_formatting.inTable) {
+        NSString *itemId = [_js.tables getSelectedTableId];
+        if (itemId == nil)
+            return;
+
+        NSMutableArray *rounded = [NSMutableArray arrayWithCapacity: widthPcts.count];
+        for (NSUInteger i = 0; i < widthPcts.count; i++) {
+            NSNumber *raw = [widthPcts objectAtIndex: i];
+            [rounded addObject: [NSNumber numberWithDouble: round(raw.doubleValue)]];
+        }
+
+        [_js.tables set: itemId colWidths: rounded];
+    }
+    
+    [self updateFormatting];
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDFileFormat.h
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDFileFormat.h b/experiments/objcFramework/EDFileFormat.h
new file mode 100644
index 0000000..d5d7417
--- /dev/null
+++ b/experiments/objcFramework/EDFileFormat.h
@@ -0,0 +1,65 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import <Foundation/Foundation.h>
+#import <Editor/DocFormats.h>
+
+typedef NSComparisonResult(^Comparator)(id,id);
+
+@class EDEditor;
+@protocol EDSystemDelegate;
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                          EDFileFormat                                          //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface EDFileFormat : NSObject
+
+@property (weak) EDEditor *editor;
+@property (copy, readonly) NSString *fileLocalPath;
+@property (copy, readonly) NSString *absoluteHTMLPath;
+@property (assign, readonly) BOOL supportsBareStyles;
+@property (copy, readonly) NSString *imagePath;
+@property (readonly) Comparator styleComparator;
+
+- (EDFileFormat *)init;
+- (NSString *)prepareForLoad:(NSError **)error;
+- (void)finishLoad;
+- (void)save:(NSString *)filename html:(NSString *)html completion:(void (^)(NSError *error))completion;
+- (void)setupInitialStyles;
+- (CSSStyle *)setupTableStyle;
+- (void)setupFigureStyle;
+- (void)setupOutlineStyle;
+- (NSString *)addImage:(NSData *)data extension:(NSString *)extension error:(NSError **)error;
+- (NSString *)localizedStyleName:(CSSStyle *)style;
+
++ (BOOL)create:(NSString *)filename error:(NSError **)error;
++ (EDFileFormat *)formatForExtension:(NSString *)extension;
++ (BOOL)isExtensionSupported:(NSString *)extension;
++ (NSString *)findUniqueFilenameWithPrefix:(NSString *)prefix
+                                 extension:(NSString *)extension
+                             existingNames:(NSArray *)names;
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDFileFormat.m
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDFileFormat.m b/experiments/objcFramework/EDFileFormat.m
new file mode 100644
index 0000000..e94d1f6
--- /dev/null
+++ b/experiments/objcFramework/EDFileFormat.m
@@ -0,0 +1,968 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import "EDFileFormat.h"
+#import "EDEditor.h"
+#import "EDHTMLTidy.h"
+#import "EDJSInterface.h"
+#import "EDTiming.h"
+#import "EDStyle.h"
+#import "EDDocumentSetup.h"
+#import "EDUtil.h"
+#import <FileClient/FCError.h>
+#import <FileClient/FCUtil.h>
+
+#define L10NStyleNameHeading1                        NSLocalizedString(@"StyleNameHeading1",NULL)
+#define L10NStyleNameHeading2                        NSLocalizedString(@"StyleNameHeading2",NULL)
+#define L10NStyleNameHeading3                        NSLocalizedString(@"StyleNameHeading3",NULL)
+#define L10NStyleNameHeading4                        NSLocalizedString(@"StyleNameHeading4",NULL)
+#define L10NStyleNameHeading5                        NSLocalizedString(@"StyleNameHeading5",NULL)
+#define L10NStyleNameHeading6                        NSLocalizedString(@"StyleNameHeading6",NULL)
+#define L10NStyleNameParagraph                       NSLocalizedString(@"StyleNameParagraph",NULL)
+#define L10NStyleNameBlockQuote                      NSLocalizedString(@"StyleNameBlockQuote",NULL)
+#define L10NStyleNamePre                             NSLocalizedString(@"StyleNamePre",NULL)
+#define L10NStyleNameBody                            NSLocalizedString(@"StyleNameBody",NULL)
+#define L10NStyleNameFigure                          NSLocalizedString(@"StyleNameFigure",NULL)
+#define L10NStyleNameFigureCaption                   NSLocalizedString(@"StyleNameFigureCaption",NULL)
+#define L10NStyleNameTable                           NSLocalizedString(@"StyleNameTable",NULL)
+#define L10NStyleNameTableCaption                    NSLocalizedString(@"StyleNameTableCaption",NULL)
+#define L10NStyleNameTableOfContents                 NSLocalizedString(@"StyleNameTableOfContents",NULL)
+#define L10NStyleNameListOfFigures                   NSLocalizedString(@"StyleNameListOfFigures",NULL)
+#define L10NStyleNameListOfTables                    NSLocalizedString(@"StyleNameListOfTables",NULL)
+
+@interface HTMLFormat : EDFileFormat
+@end
+
+@interface TextFormat : EDFileFormat
+@end
+
+@interface MarkdownFormat : EDFileFormat
+@end
+
+@interface LaTeXFormat : EDFileFormat
+@end
+
+@interface WordFormat : EDFileFormat
+@end
+
+static NSString *HTML_localizedTagName(Tag tag)
+{
+    switch (tag) {
+        case HTML_H1: return L10NStyleNameHeading1;
+        case HTML_H2: return L10NStyleNameHeading2;
+        case HTML_H3: return L10NStyleNameHeading3;
+        case HTML_H4: return L10NStyleNameHeading4;
+        case HTML_H5: return L10NStyleNameHeading5;
+        case HTML_H6: return L10NStyleNameHeading6;
+        case HTML_P: return L10NStyleNameParagraph;
+        case HTML_BLOCKQUOTE: return L10NStyleNameBlockQuote;
+        case HTML_PRE: return L10NStyleNamePre;
+        case HTML_BODY: return L10NStyleNameBody;
+        case HTML_FIGURE: return L10NStyleNameFigure;
+        case HTML_FIGCAPTION: return L10NStyleNameFigureCaption;
+        case HTML_TABLE: return L10NStyleNameTable;
+        case HTML_CAPTION: return L10NStyleNameTableCaption;
+        default: return nil;
+    }
+}
+
+NSString *HTML_uiNameForSelector(const char *ident)
+{
+    NSString *rawSelector = NSStringFromC(ident);
+    if ([rawSelector isEqualToString: @"nav.tableofcontents"])
+        return L10NStyleNameTableOfContents;
+    if ([rawSelector isEqualToString: @"nav.listoffigures"])
+        return L10NStyleNameListOfFigures;
+    if ([rawSelector isEqualToString: @"nav.listoftables"])
+        return L10NStyleNameListOfTables;
+
+    NSString *elementUIName = HTML_localizedTagName(CSSSelectorGetTag(ident));
+    if (elementUIName == nil)
+        return nil;
+
+    NSString *result = NULL;
+    char *className = CSSSelectorCopyClassName(ident);
+
+    if (!CSSSelectorHasClassName(ident))
+        result = elementUIName;
+    else if (CSSSelectorGetTag(ident) == HTML_P)
+        result = NSStringFromC(className);
+    else
+        result = [NSString stringWithFormat: @"%@ (%@)", elementUIName, NSStringFromC(className)];
+
+    free(className);
+    return result;
+}
+
+@interface NSOutputStream(CompleteWrite)
+
+- (int)completeWrite:(const uint8_t *)buffer length:(NSUInteger)length;
+
+@end
+
+@implementation NSOutputStream(CompleteWrite)
+
+- (int)completeWrite:(const uint8_t *)buffer length:(NSUInteger)length
+{
+    NSUInteger offset = 0;
+    do {
+        NSUInteger remaining = length - offset;
+        NSInteger written = [self write: &buffer[offset] maxLength: remaining];
+        if (written <= 0)
+            return 0;
+        offset += written;
+    } while (offset < length);
+    return 1;
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                          EDFileFormat                                          //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation EDFileFormat
+
+- (EDFileFormat *)init
+{
+    if (!(self = [super init]))
+        return nil;
+    return self;
+}
+
+- (NSString *)fileLocalPath
+{
+    return self.editor.delegate.editorPath;
+}
+
+- (BOOL)supportsBareStyles
+{
+    return NO;
+}
+
+- (NSString *)absoluteHTMLPath
+{
+    [NSException raise: @"FileFormat" format: @"%@ absoluteHTMLPath not implemented", self.class];
+    return nil;
+}
+
+- (void)save:(NSString *)filename html:(NSString *)html completion:(void (^)(NSError *error))completion
+{
+    completion([NSError error: @"FileFormat save not implemented"]);
+}
+
+- (NSString *)prepareForLoad:(NSError **)error
+{
+    [NSException raise: @"FileFormat" format: @"%@ prepareForLoad not implemented", self.class];
+    return nil;
+}
+
+- (void)finishLoad
+{
+    [NSException raise: @"FileFormat" format: @"%@ finishLoad not implemented", self.class];
+}
+
++ (BOOL)create2:(NSString *)filename error:(DFError **)error
+{
+    NSString *extension = filename.pathExtension.lowercaseString;
+    if ([extension isEqualToString: @"html"] || [extension isEqualToString: @"htm"])
+        return DFHTMLCreateDefaultFile(filename.UTF8String,error);
+    else if ([extension isEqualToString: @"docx"]) {
+        return DFWordCreateDefault(filename.UTF8String,error);
+    }
+
+    DFErrorFormat(error,"Unsupported file format: %s\n",extension.UTF8String);
+    return NO;
+}
+
++ (BOOL)create:(NSString *)filename error:(NSError **)error
+{
+    DFError *dferror = NULL;
+    BOOL ok = [self create2: filename error: &dferror];
+    if (!ok)
+        DFErrorReleaseToNSError(dferror,error);
+    return ok;
+}
+
++ (EDFileFormat *)formatForExtension:(NSString *)extension
+{
+    extension = extension.lowercaseString;
+    if ([extension isEqualToString: @"html"] || [extension isEqualToString: @"htm"])
+        return [[HTMLFormat alloc] init];
+    else if ([extension isEqualToString: @"txt"])
+        return [[TextFormat alloc] init];
+    else if ([extension isEqualToString: @"md"])
+        return [[MarkdownFormat alloc] init];
+    else if ([extension isEqualToString: @"tex"])
+        return [[LaTeXFormat alloc] init];
+    else if ([extension isEqualToString: @"docx"])
+        return [[WordFormat alloc] init];
+    return nil;
+}
+
++ (BOOL)isExtensionSupported:(NSString *)extension
+{
+    return ([self formatForExtension: extension] != nil);
+}
+
+- (void)setupInitialStyles
+{
+    // Ensure there is at least one style present for a given heading level
+    NSMutableSet *allElementNames = [NSMutableSet setWithCapacity: 0];
+    CSSSheet *styleSheet = self.editor.styleSheet;
+    const char **allSelectors = CSSSheetCopySelectors(styleSheet);
+    for (int i = 0; allSelectors[i]; i++) {
+        CSSStyle *style = CSSSheetLookupSelector(styleSheet,allSelectors[i],NO,NO);
+        [allElementNames addObject: NSStringFromC(style->elementName)];
+    }
+    free(allSelectors);
+
+    for (int i = 1; i <= 6; i++) {
+        NSString *elementName = [NSString stringWithFormat: @"h%d", i];
+        if (![allElementNames containsObject: elementName])
+            CSSSheetLookupElement(styleSheet,elementName.UTF8String,NULL,YES,YES);
+    }
+}
+
+- (CSSStyle *)setupTableStyle
+{
+    int changed = NO;
+    CSSSheet *styleSheet = self.editor.styleSheet;
+    CSSStyle *result = WordSetupTableGridStyle(styleSheet,&changed);
+    if (changed)
+        [self.editor updateCSS];
+    return result;
+}
+
+- (void)setupFigureStyle
+{
+    CSSStyle *figure = CSSSheetLookupElement(self.editor.styleSheet,"figure",NULL,NO,NO);
+    if ((figure == nil) || CSSStyleIsEmpty(figure)) {
+        figure = CSSSheetLookupElement(self.editor.styleSheet,"figure",NULL,YES,NO);
+        CSSProperties *rule = CSSStyleRule(figure);
+        CSSPut(rule,"text-align","center");
+        CSSPut(rule,"margin-left","auto");
+        CSSPut(rule,"margin-right","auto");
+        CSSPut(rule,"margin-top","12pt");
+        CSSPut(rule,"margin-bottom","12pt");
+        figure->latent = NO;
+        [self.editor updateCSS];
+    }
+}
+
+- (void)setupOutlineStyle
+{
+}
+
+- (NSString *)imagePath
+{
+    return [self.fileLocalPath stringByDeletingLastPathComponent];
+}
+
++ (NSString *)findUniqueFilenameWithPrefix:(NSString *)prefix
+                                 extension:(NSString *)extension
+                             existingNames:(NSArray *)names
+{
+    // We first need to obtain a listing of all files in the directory, because we want a name
+    // for which there is no other file with that name and a different extension
+    // (e.g. image001.jpg and image001.png)
+
+    NSMutableSet *existingNames = [NSMutableSet setWithCapacity: 0];
+    for (NSString *filename in names)
+        [existingNames addObject: [filename.lowercaseString stringByDeletingPathExtension]];
+
+    int num = 1;
+    NSString *candidate;
+    do {
+        candidate = [NSString stringWithFormat: @"%@%03d", prefix, num];
+        num++;
+    } while ([existingNames containsObject: candidate.lowercaseString]);
+
+    return [candidate stringByAppendingPathExtension: extension];
+}
+
+- (NSString *)addImage:(NSData *)data extension:(NSString *)extension error:(NSError **)error
+{
+    NSString *baseName = [self.fileLocalPath.lastPathComponent stringByDeletingPathExtension];
+    NSString *prefix = [NSString stringWithFormat: @"%@_image", baseName];
+    return [self.editor.delegate saveResource: data prefix: prefix extension: extension error: error];
+}
+
+- (NSString *)localizedStyleName:(CSSStyle *)style
+{
+    char *cDisplayName = CSSStyleCopyDisplayName(style);
+    if (cDisplayName != NULL) {
+        NSString *displayName = NSStringFromC(cDisplayName);
+        free(cDisplayName);
+        return displayName;
+    }
+    NSString *str = HTML_uiNameForSelector(style->selector);
+    if (str != nil)
+        return str;
+    return HTML_uiNameForSelector(style->selector);
+}
+
+- (Comparator)styleComparator
+{
+    return ^NSComparisonResult(id obj1, id obj2) {
+        EDStyle *style1 = (EDStyle *)obj1;
+        EDStyle *style2 = (EDStyle *)obj2;
+        NSString *name1 = [self localizedStyleName: style1.cssStyle];
+        NSString *name2 = [self localizedStyleName: style2.cssStyle];
+        return [name1 localizedCaseInsensitiveCompare: name2];
+    };
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                           HTMLFormat                                           //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation HTMLFormat
+
+- (HTMLFormat *)init
+{
+    if (!(self = [super init]))
+        return nil;
+    return self;
+}
+
+- (BOOL)supportsBareStyles
+{
+    return YES;
+}
+
+- (NSString *)absoluteHTMLPath
+{
+    return self.fileLocalPath;
+}
+
+- (NSSet *)builtinRawSelectors
+{
+    return [NSSet setWithObjects:
+            @"h1",
+            @"h2",
+            @"h3",
+            @"h4",
+            @"h5",
+            @"h6",
+            @"p",
+            @"pre",
+            @"blockquote",
+            @"@page",
+            @"body",
+            @"figure",
+            @"figcaption",
+            @"table",
+            @"caption",
+            @"nav.tableofcontents",
+            @"nav.listoffigures",
+            @"nav.listoftables",
+            nil];
+}
+
+- (NSString *)prepareForLoad:(NSError **)error
+{
+    // Nothing to do here - just return the name of the HTML file to load
+    return self.fileLocalPath;
+}
+
+- (void)finishLoad
+{
+    // Nothing to do here - default initialisation is sufficient
+}
+
+- (void)save:(NSString *)filename html:(NSString *)html completion:(void (^)(NSError *error))completion
+{
+    completion = [completion copy];
+    NSString *extension = self.fileLocalPath.pathExtension.lowercaseString;
+    BOOL isXHTML = [extension isEqualToString: @"xml"] || [extension isEqualToString: @"xhtml"];
+    [self.editor.saveTiming addEntry: @"JS getHTML"];
+    if (html.length == 0) {
+        completion([NSError error: @"HTML code is 0 bytes"]);
+        return;
+    }
+    html = [@"<!DOCTYPE html>\n" stringByAppendingString: html];
+
+    NSData *rawData = [html dataUsingEncoding: NSUTF8StringEncoding];
+
+    [EDHTMLTidy tidy: rawData isXHTML: isXHTML editor: self.editor completion:^(NSData *output, NSError *error) {
+        [self.editor.saveTiming addEntry: @"HTML Tidy"];
+
+        if (error != nil) {
+            completion(error);
+            return;
+        }
+
+        if ((output == nil) || (output.length == 0)) {
+            completion([NSError error: @"Output of HTML Tidy is 0 bytes"]);
+            return;
+        }
+
+        NSOutputStream *out = [NSOutputStream outputStreamToFileAtPath: filename append: NO];
+        [out open];
+        [out completeWrite: output.bytes length: output.length];
+        [out close];
+
+        if (out.streamError != nil) {
+            completion(out.streamError);
+            return;
+        }
+        [self.editor.saveTiming addEntry: @"Write temp file"];
+        completion(nil);
+    }];
+}
+
+- (BOOL)setupCellStyle:(NSString *)elementName
+{
+    CSSStyle *style = CSSSheetLookupElement(self.editor.styleSheet,elementName.UTF8String,NULL,NO,NO);
+    if (style != nil)
+        return NO;
+    style = CSSSheetLookupElement(self.editor.styleSheet,elementName.UTF8String,NULL,YES,NO);
+
+    CSSProperties *base = CSSStyleRuleForSuffix(style,"");
+    CSSProperties *firstChild = CSSStyleRuleForSuffix(style," > :first-child");
+    CSSProperties *lastChild = CSSStyleRuleForSuffix(style," > :last-child");
+
+    CSSPut(base,"border-left-width","1px");
+    CSSPut(base,"border-right-width","1px");
+    CSSPut(base,"border-top-width","1px");
+    CSSPut(base,"border-bottom-width","1px");
+
+    CSSPut(base,"border-left-style","solid");
+    CSSPut(base,"border-right-style","solid");
+    CSSPut(base,"border-top-style","solid");
+    CSSPut(base,"border-bottom-style","solid");
+
+    CSSPut(base,"border-left-color","black");
+    CSSPut(base,"border-right-color","black");
+    CSSPut(base,"border-top-color","black");
+    CSSPut(base,"border-bottom-color","black");
+
+    CSSPut(firstChild,"margin-top","0");
+    CSSPut(lastChild,"margin-bottom","0");
+    return YES;
+}
+
+- (void)fixCorruptUXWrite10CSS
+{
+    // Document was edited in UX Write >= 1.1.0 and then later edited in UX Write 1.0.x
+    // This results in corrupted counter-increment, counter-reset, and content values,
+    // due to a bug in UX Write 1.0.x which serialises these incorrectly into the stylesheet
+
+    // counter-increment and counter-reset are invalid, because WebKit's CSS serialisation
+    // code (used by 1.0.x to generate the stylesheet text for these) places commas between
+    // all of the values, which (I think) is not valid CSS
+
+    // counter is invalid, because Styles_discoverStyles() in 1.0.x stupidly removes single
+    // and double quotes from the start of any property values, on the assumption that any
+    // such values are whole strings. This turns the following:
+    //
+    // h1::before { content: counter(h1) ". " }
+    //
+    // into:
+    //
+    // h1::before { content: counter(h1) " }
+    //
+    // Because CSS strings can't end in a newline, the parser discards the rest of the
+    // property, and the ::before rule becomes empty
+
+    // To get around these problems, we simply delete all the counter-increment,
+    // counter-reset, and content properties, and re-create them. This is only going to be
+    // a problem in a very small number of cases where a document is taken from 1.1.x back
+    // to 1.0.x, e.g. due to the upgrade not being completed on all devices
+
+    CSSSheet *styleSheet = self.editor.styleSheet;
+
+    // First determine if heading numbering was on
+    // FIXME: Won't work with Word documents, in which all styles have a class name
+    CSSStyle *h1 = CSSSheetLookupElement(styleSheet,"h1",NULL,NO,NO);
+    BOOL hadHeadingNumbering = (CSSGet(CSSStyleRule(h1),"counter-increment") != NULL);
+
+    // Now clear all the counter-increment, counter-reset, and content properties
+    const char **allSelectors = CSSSheetCopySelectors(styleSheet);
+    for (int selIndex = 0; allSelectors[selIndex]; selIndex++) {
+        const char *selector = allSelectors[selIndex];
+        CSSStyle *style = CSSSheetLookupSelector(styleSheet,selector,NO,NO);
+
+        const char **allSuffixes = CSSStyleCopySuffixes(style);
+        for (int suffixIndex = 0; allSuffixes[suffixIndex]; suffixIndex++) {
+            const char *suffix = allSuffixes[suffixIndex];
+            CSSProperties *properties = CSSStyleRuleForSuffix(style,suffix);
+            CSSPut(properties,"counter-increment",NULL);
+            CSSPut(properties,"counter-reset",NULL);
+            CSSPut(properties,"content",NULL);
+        }
+        free(allSuffixes);
+    }
+    free(allSelectors);
+
+    // If the document *was* using heading numbering, turn it on again
+    if (hadHeadingNumbering)
+        CSSSheetSetHeadingNumbering(self.editor.styleSheet,YES);
+}
+
+- (void)transitionFromUXWrite10Numbering
+{
+    if (CSSSheetIsNumberingUsed(self.editor.styleSheet)) {
+        // Document was edited in UX Write >= 1.1.0 and then later edited in UX Write 1.0.x
+        [self fixCorruptUXWrite10CSS];
+    }
+    else {
+        // Document was created in UX Write 1.0.x and is being opened in 1.1.x for the first time
+        // Switch to using CSS counters for numbering
+        BOOL sectionNumbering = [self.editor.js.outline detectSectionNumbering];
+        if (sectionNumbering)
+            CSSSheetSetHeadingNumbering(self.editor.styleSheet,YES);
+    }
+}
+
+- (void)setupInitialStyles
+{
+    [super setupInitialStyles];
+
+    for (NSString *rawSelector in self.builtinRawSelectors)
+        CSSSheetLookupSelector(self.editor.styleSheet,rawSelector.UTF8String,YES,YES);
+
+    // Styles
+    CSSStyle *p = CSSSheetLookupElement(self.editor.styleSheet,"p",NULL,YES,NO);
+    CSSSheetSetDefaultStyle(self.editor.styleSheet,p,StyleFamilyParagraph);
+
+    NSString *origGenerator = self.editor.origGenerator;
+    if ((origGenerator != nil) && [origGenerator hasPrefix: @"UX Write 1.0"])
+        [self transitionFromUXWrite10Numbering];
+}
+
+- (CSSStyle *)setupTableStyle
+{
+//    td-paragraph-margins
+//        td > :first-child { margin-top: 0; }
+//        td > :last-child { margin-bottom: 0; }
+//    th-paragraph-margins
+//        th > :first-child { margin-top: 0; }
+//        th > :last-child { margin-bottom: 0; }
+//    table-borders
+//        table { border-collapse: collapse; margin-left: auto; margin-right: auto; }
+//        td { border: 1px solid black; }
+//        th { border: 1px solid black; }
+//    table-caption
+//        caption { caption-side: bottom; }
+
+    BOOL changed = NO;
+
+    if ([self setupCellStyle: @"td"])
+        changed = YES;
+    if ([self setupCellStyle: @"th"])
+        changed = YES;
+
+    CSSStyle *table = CSSSheetLookupElement(self.editor.styleSheet,"table",NULL,NO,NO);
+    if ((table == nil) || CSSStyleIsEmpty(table)) {
+        table = CSSSheetLookupElement(self.editor.styleSheet,"table",NULL,YES,NO);
+        CSSProperties *rule = CSSStyleRule(table);
+        CSSPut(rule,"border-collapse","collapse");
+        CSSPut(rule,"margin-left","auto");
+        CSSPut(rule,"margin-right","auto");
+        changed = YES;
+    }
+
+    if (table->latent) {
+        table->latent = NO;
+        changed = YES;
+    }
+
+    // FIXME: this is already done in useCSSNumbering - need to ignore duplication
+    CSSStyle *caption = CSSSheetLookupElement(self.editor.styleSheet,"caption",NULL,NO,NO);
+    if ((caption == nil) || CSSStyleIsEmpty(caption)) {
+        caption = CSSSheetLookupElement(self.editor.styleSheet,"caption",NULL,YES,NO);
+        CSSPut(CSSStyleRule(caption),"caption-side","bottom");
+        changed = YES;
+    }
+
+    if (changed)
+        [self.editor updateCSS];
+
+    return table;
+}
+
+- (void)setupOutlineStyle
+{
+    BOOL changed = NO;
+
+    CSSStyle *style = CSSSheetLookupElement(self.editor.styleSheet,"p","toc1",NO,NO);
+    if (style == nil) {
+        style = CSSSheetLookupElement(self.editor.styleSheet,"p","toc1",YES,NO);
+        CSSProperties *rule = CSSStyleRule(style);
+        CSSPut(rule,"margin-bottom","6pt");
+        CSSPut(rule,"margin-left","0pt");
+        CSSPut(rule,"margin-top","12pt");
+        changed = YES;
+    }
+
+    style = CSSSheetLookupElement(self.editor.styleSheet,"p","toc2",NO,NO);
+    if (style == nil) {
+        style = CSSSheetLookupElement(self.editor.styleSheet,"p","toc2",YES,NO);
+        CSSProperties *rule = CSSStyleRule(style);
+        CSSPut(rule,"margin-bottom","6pt");
+        CSSPut(rule,"margin-left","24pt");
+        CSSPut(rule,"margin-top","6pt");
+        changed = YES;
+    }
+
+    style = CSSSheetLookupElement(self.editor.styleSheet,"p","toc3",NO,NO);
+    if (style == nil) {
+        style = CSSSheetLookupElement(self.editor.styleSheet,"p","toc3",YES,NO);
+        CSSProperties *rule = CSSStyleRule(style);
+        CSSPut(rule,"margin-bottom","6pt");
+        CSSPut(rule,"margin-left","48pt");
+        CSSPut(rule,"margin-top","6pt");
+        changed = YES;
+    }
+
+    if (changed)
+        [self.editor updateCSS];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                           TextFormat                                           //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation TextFormat
+
+- (TextFormat *)init
+{
+    if (!(self = [super init]))
+        return nil;
+    return self;
+}
+
+- (void)save:(NSString *)filename html:(NSString *)html completion:(void (^)(NSError *error))completion
+{
+    completion([NSError error: @"TextFormat save not implemented"]);
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                         MarkdownFormat                                         //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation MarkdownFormat
+
+- (MarkdownFormat *)init
+{
+    if (!(self = [super init]))
+        return nil;
+    return self;
+}
+
+- (void)save:(NSString *)filename html:(NSString *)html completion:(void (^)(NSError *error))completion
+{
+    completion([NSError error: @"MarkdownFormat save not implemented"]);
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                           LaTeXFormat                                          //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation LaTeXFormat
+
+- (LaTeXFormat *)init
+{
+    if (!(self = [super init]))
+        return nil;
+    return self;
+}
+
+- (void)save:(NSString *)filename html:(NSString *)html completion:(void (^)(NSError *error))completion
+{
+    completion([NSError error: @"LaTeXFormat save not implemented"]);
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                           WordFormat                                           //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface WordFormat()
+
+@property (copy) NSString *tempDir;
+@property (copy) NSString *idPrefix;
+
+@property (copy, readonly) NSString *originalBackupPath;
+@property (copy, readonly) NSString *htmlTempDir;
+@property (copy, readonly) NSString *htmlTempFilename;
+
+@end
+
+@implementation WordFormat
+
+- (WordFormat *)init
+{
+    if (!(self = [super init]))
+        return nil;
+    _idPrefix = [[NSString alloc] initWithFormat: @"bdt%u-", arc4random()];
+    return self;
+}
+
+- (void)dealloc
+{
+    if (_tempDir != nil)
+        [[NSFileManager defaultManager] removeItemAtPath: _tempDir error: nil];
+}
+
+- (NSString *)originalBackupPath
+{
+    return [self.tempDir stringByAppendingPathComponent: @"original.docx"];
+}
+
+- (NSString *)packageTempDir
+{
+    return [self.tempDir stringByAppendingPathComponent: @"package"];
+}
+
+- (NSString *)htmlTempDir
+{
+    return [self.tempDir stringByAppendingPathComponent: @"html"];
+}
+
+- (NSString *)htmlTempFilename
+{
+    return [self.htmlTempDir stringByAppendingPathComponent: @"document.html"];
+}
+
+- (NSString *)absoluteHTMLPath
+{
+    return self.htmlTempFilename;
+}
+
+- (NSString *)prepareForLoad:(NSError **)error
+{
+    self.tempDir = FCCreateTemporaryDirectory(error);
+    if (self.tempDir == nil)
+        return nil;
+
+    NSFileManager *fm = [NSFileManager defaultManager];
+    if (![fm createDirectoryAtPath: self.htmlTempDir withIntermediateDirectories: YES
+                        attributes: nil error: error])
+        return nil;
+
+    NSString *resetSource = [[NSBundle mainBundle] pathForResource: @"reset" ofType: @"css"];
+    assert(resetSource != nil);
+    NSString *resetDest = [self.htmlTempDir stringByAppendingPathComponent: @"reset.css"];
+    if (![fm copyItemAtPath: resetSource toPath: resetDest error: error])
+        return nil;
+
+    if (![fm copyItemAtPath: self.fileLocalPath toPath: self.originalBackupPath error: error])
+        return nil;
+
+    DFError *dferror = NULL;
+    DFConcreteDocument *concreteDoc = DFConcreteDocumentOpenFile(self.originalBackupPath.UTF8String,&dferror);
+    if (concreteDoc == NULL) {
+        DFErrorReleaseToNSError(dferror,error);
+        return nil;
+    }
+
+    DFStorage *abstractPackage = DFStorageNewFilesystem(self.htmlTempDir.UTF8String,DFFileFormatHTML);
+    DFAbstractDocument *abstractDoc = DFAbstractDocumentNew(abstractPackage);
+
+    int ok = DFGet(concreteDoc,abstractDoc,&dferror);
+    DFDocument *htmlDoc = NULL;
+    if (ok)
+        htmlDoc = DFDocumentRetain(DFAbstractDocumentGetHTML(abstractDoc));
+
+    DFStorageRelease(abstractPackage);
+    DFAbstractDocumentRelease(abstractDoc);
+    DFConcreteDocumentRelease(concreteDoc);
+
+    if (!ok) {
+        DFErrorReleaseToNSError(dferror,error);
+        return nil;
+    }
+
+    if (htmlDoc == NULL) {
+        DFErrorFormat(&dferror,"htmlDoc is NULL");
+        DFErrorReleaseToNSError(dferror,error);
+        return nil;
+    }
+
+
+    if (!DFSerializeXMLFile(htmlDoc,0,0,self.htmlTempFilename.UTF8String,&dferror)) {
+        DFErrorReleaseToNSError(dferror,error);
+        DFDocumentRelease(htmlDoc);
+        return nil;
+    }
+
+    DFDocumentRelease(htmlDoc);
+    return self.htmlTempFilename;
+}
+
+- (void)finishLoad
+{
+}
+
+// This method is always run in a background thread, initiated via save
+- (BOOL)directSave:(NSString *)filename html:(NSString *)html error:(NSError **)error
+{
+    DFError *dferror = NULL;
+    DFDocument *htmlDoc = DFParseHTMLString(html.UTF8String,1,&dferror);
+    if (htmlDoc == nil) {
+        DFErrorReleaseToNSError(dferror,error);
+        FCErrorPrepend(error,@"Parse HTML");
+        return NO;
+    }
+
+    // Write out the HTML file in case wer're printing or generating a PDF, so it can load the
+    // page content from that
+    // FIXME: In this case, we don't really need to save the word document itself - saving *just*
+    // the HTML will be sufficient. And when we are actually saving the word document, we don't
+    // need to write out the HTML data. So this is just a temporary solution.
+    DFError *localError = NULL;
+    if (!DFSerializeXMLFile(htmlDoc,0,0,self.htmlTempFilename.UTF8String,&localError)) {
+        DFErrorReleaseToNSError(localError,error);
+        FCErrorPrepend(error,@"Save HTML");
+        DFDocumentRelease(htmlDoc);
+        return NO;
+    }
+
+    NSString *modifiedPath = [self.tempDir stringByAppendingPathComponent: @"modified.docx"];
+    NSFileManager *fm = [NSFileManager defaultManager];
+    if (![fm copyItemAtPath: self.originalBackupPath toPath: modifiedPath error: error]) {
+        FCErrorPrepend(error,@"Copy original to modified");
+        return NO;
+    }
+
+    DFConcreteDocument *concreteDoc = DFConcreteDocumentOpenFile(modifiedPath.UTF8String,&dferror);
+    if (concreteDoc == NULL) {
+        DFErrorReleaseToNSError(dferror,error);
+        FCErrorPrepend(error,@"Open document for update");
+        return NO;
+    }
+
+    BOOL ok = NO;
+
+    DFStorage *abstractPackage = DFStorageNewFilesystem(self.htmlTempDir.UTF8String,DFFileFormatHTML);
+    DFAbstractDocument *abstractDoc = DFAbstractDocumentNew(abstractPackage);
+
+    DFAbstractDocumentSetHTML(abstractDoc,htmlDoc);
+
+    if (!DFPut(concreteDoc,abstractDoc,&dferror)) {
+        DFErrorReleaseToNSError(dferror,error);
+        FCErrorPrepend(error,@"DFPut");
+        goto end;
+    }
+
+    if ([fm fileExistsAtPath: filename] && ![fm removeItemAtPath: filename error: error]) {
+        FCErrorPrepend(error,@"Remove old version of document");
+        goto end;
+    }
+
+    if (![fm moveItemAtPath: modifiedPath toPath: filename error: error]) {
+        FCErrorPrepend(error,@"Move new version of document into place");
+        goto end;
+    }
+
+    ok = YES;
+
+end:
+    DFDocumentRelease(htmlDoc);
+    DFStorageRelease(abstractPackage);
+    DFAbstractDocumentRelease(abstractDoc);
+    DFConcreteDocumentRelease(concreteDoc);
+    return ok;
+}
+
+- (void)save:(NSString *)filename html:(NSString *)html completion:(void (^)(NSError *error))completion
+{
+    // Create a copy of the completion on the heap, in case we're passed one that's allocated on
+    // the stack
+    completion = [completion copy];
+    [self.editor.system runCommandInBackground:^BOOL(NSError **commandError) {
+        return [self directSave: filename html: html error: commandError];
+    } completion:^(NSError *completionError) {
+        completion(completionError);
+    }];
+}
+
+- (NSString *)imagePath
+{
+    return self.htmlTempDir;
+}
+
+- (void)setupInitialStyles
+{
+    [super setupInitialStyles];
+    [self.editor.js.styles setParagraphClass: @"Normal"];
+}
+
+- (NSString *)addImage:(NSData *)data extension:(NSString *)extension error:(NSError **)error
+{
+    NSFileManager *fm = [NSFileManager defaultManager];
+    NSArray *contents = [fm contentsOfDirectoryAtPath: self.imagePath error: error];
+    if (contents == nil)
+        return nil;
+
+    NSString *filename = [EDFileFormat findUniqueFilenameWithPrefix: @"image"
+                                                        extension: extension
+                                                    existingNames: contents];
+
+    NSString *fullPath = [self.imagePath stringByAppendingPathComponent: filename];
+    if (![data writeToFile: fullPath options: 0 error: error])
+        return nil;
+
+    return fullPath;
+}
+
+- (NSString *)localizedStyleName:(CSSStyle *)style
+{
+    char *cDisplayName = CSSStyleCopyDisplayName(style);
+    if (cDisplayName != nil) {
+        NSString *displayName = NSStringFromC(cDisplayName);
+        free(cDisplayName);
+        return [[NSBundle mainBundle] localizedStringForKey: displayName.lowercaseString
+                                                      value: displayName
+                                                      table: @"WordBuiltinStyles"];
+    }
+
+    return [super localizedStyleName: style];
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDFindReplace.h
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDFindReplace.h b/experiments/objcFramework/EDFindReplace.h
new file mode 100644
index 0000000..50d4fac
--- /dev/null
+++ b/experiments/objcFramework/EDFindReplace.h
@@ -0,0 +1,48 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import <Foundation/Foundation.h>
+
+#import "EDScan.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                         EDFindOperation                                        //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface EDFindOperation : EDScanOperation
+
+@property (copy, readonly) NSString *findTerm;
+@property (copy, readonly) NSString *replaceTerm;
+@property (assign, readonly) BOOL caseSensitive;
+@property (strong, readonly) NSRegularExpression *regex;
+
+- (EDFindOperation *)initWithEditor:(EDEditor *)editor
+                           findTerm:(NSString *)findTerm
+                        replaceTerm:(NSString *)replaceTerm
+                      caseSensitive:(BOOL)caseSensitive
+                              regex:(NSRegularExpression *)regex;
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDFindReplace.m
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDFindReplace.m b/experiments/objcFramework/EDFindReplace.m
new file mode 100644
index 0000000..59cc151
--- /dev/null
+++ b/experiments/objcFramework/EDFindReplace.m
@@ -0,0 +1,82 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import "EDFindReplace.h"
+#import "EDEditor.h"
+#import "EDScanResults.h"
+#import "EDJSInterface.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                         EDFindOperation                                        //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation EDFindOperation
+
+- (EDFindOperation *)initWithEditor:(EDEditor *)editor
+                           findTerm:(NSString *)findTerm
+                        replaceTerm:(NSString *)replaceTerm
+                      caseSensitive:(BOOL)caseSensitive
+                              regex:(NSRegularExpression *)regex
+{
+    if (!(self = [super initWithEditor: editor]))
+        return nil;
+    _findTerm = [findTerm copy];
+    _replaceTerm = [replaceTerm copy];
+    _caseSensitive = caseSensitive;
+    _regex = regex;
+    return self;
+}
+
+- (void)processParagraph:(EDScanParagraph *)paragraph
+{
+    NSString *text = paragraph.text;
+
+    NSMutableArray *newMatches = [NSMutableArray arrayWithCapacity: 0];
+
+    NSStringCompareOptions compareOptions = _caseSensitive ? 0 : NSCaseInsensitiveSearch;
+    NSUInteger index = 0;
+    while (true) {
+        NSRange remainder = NSMakeRange(index,text.length-index);
+        NSRange range;
+        if (_regex != nil)
+            range = [_regex rangeOfFirstMatchInString: text options: 0 range: remainder];
+        else
+            range = [text rangeOfString: _findTerm options: compareOptions range: remainder];
+        if (range.location == NSNotFound)
+            break;
+        index = range.location + range.length;
+
+        [self includeCurrentSection];
+
+        int matchId = [self.editor.js.scan addMatchStart: (int)range.location end: (int)(range.location + range.length)];
+        NSString *matchText = [text substringWithRange: range];
+        [newMatches addObject: [[EDScanMatch alloc] initWithMatchId: matchId text: matchText]];
+    }
+
+    [self foundMatches: newMatches];
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9f851a34/experiments/objcFramework/EDGeometry.h
----------------------------------------------------------------------
diff --git a/experiments/objcFramework/EDGeometry.h b/experiments/objcFramework/EDGeometry.h
new file mode 100644
index 0000000..cba3101
--- /dev/null
+++ b/experiments/objcFramework/EDGeometry.h
@@ -0,0 +1,80 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// This file comes from the portion of the UX Write editor that
+// works on both Apple platforms (that is, it can run on either
+// OS X or iOS). It's in the repository for illustrative purposes
+// only, to assist with the creation of the framework for the
+// Corinthia editor UI. The code does not compile independently in
+// its present form.
+
+#import <Foundation/Foundation.h>
+#import <CoreGraphics/CoreGraphics.h>
+
+typedef enum {
+    EDSizeTypeNone = 0,
+    EDSizeTypeTopLeft = 0x01,
+    EDSizeTypeTopRight = 0x02,
+    EDSizeTypeBottomLeft = 0x04,
+    EDSizeTypeBottomRight = 0x08,
+    EDSizeTypeInnerTop = 0x10,
+    EDSizeTypeInnerBottom = 0x20,
+} EDSizeType;
+
+typedef enum {
+    EDResizeEdgeTop = EDSizeTypeTopLeft | EDSizeTypeTopRight,
+    EDResizeEdgeBottom = EDSizeTypeBottomLeft | EDSizeTypeBottomRight,
+    EDResizeEdgeLeft = EDSizeTypeTopLeft | EDSizeTypeBottomLeft,
+    EDResizeEdgeRight = EDSizeTypeTopRight | EDSizeTypeBottomRight,
+    EDResizeEdgeInner = EDSizeTypeInnerTop | EDSizeTypeInnerBottom,
+} EDResizeEdge;
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                         EDItemGeometry                                         //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface EDItemGeometry : NSObject
+
+@property (assign, readonly) CGRect contentRect; // Doc's rect for figure or table (view is slightly bigger, for resize handles)
+@property (assign, readonly) CGRect fullRect; // Includes the caption
+@property (assign, readonly) CGRect parentRect; // Rect of the figure or table's containing block (usually the body)
+@property (assign, readonly) BOOL hasCaption;
+
+// columnWidths contains NSNumber (double) objects.
+// No specific value range; calculated relative to total.
+// This property should be nil for figures, in which case no inner handles will be shown
+@property (strong, readonly) NSArray *columnWidths;
+@property (strong, readonly) NSArray *columnOffsets;
+
++ (EDItemGeometry *)fromDict:(NSDictionary *)dict;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//                                                                                                //
+//                                        EDResizeDelegate                                        //
+//                                                                                                //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@protocol EDResizeDelegate <NSObject>
+
+- (void)resizedWidthPct:(CGFloat)widthPct;
+- (void)resizedColumns:(NSArray *)widthPcts;
+
+@end