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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion iosMath/lib/MTMathListIndex.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ typedef NS_ENUM(unsigned int, MTMathListSubIndexType) {
/// The subindex indexes into the radicand (only valid for radicals)
kMTSubIndexTypeRadicand,
/// The subindex indexes into the degree (only valid for radicals)
kMTSubIndexTypeDegree
kMTSubIndexTypeDegree,
/// The subindex indexes into the inner list (only valid for inner)
kMTSubIndexTypeInner
};


Expand Down
25 changes: 24 additions & 1 deletion iosMath/render/MTMathListDisplay.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ typedef NS_ENUM(unsigned int, MTLinePosition) {
/// Positioned at a subscript
kMTLinePositionSubscript,
/// Positioned at a superscript
kMTLinePositionSuperscript
kMTLinePositionSuperscript,
/// Positioned at an inner
kMTLinePositionInner
};

/// Where the line is positioned
Expand Down Expand Up @@ -185,4 +187,25 @@ typedef NS_ENUM(unsigned int, MTLinePosition) {

@end

/// Rendering of an list with delimiters
@interface MTInnerDisplay : MTDisplay

- (instancetype)init NS_UNAVAILABLE;

/** A display representing the inner list that can be wrapped in delimiters.
It's position is relative to the parent is not treated as a sub-display.
*/
@property (nonatomic, readonly) MTMathListDisplay* inner;

/** A display representing the delimiters. Their position is relative
to the parent are not treated as a sub-display.
*/
@property (nonatomic, readonly, nullable) MTDisplay* leftDelimiter;
@property (nonatomic, readonly, nullable) MTDisplay* rightDelimiter;

/// Denotes the location in the parent MTList.
@property (nonatomic, readonly) NSUInteger index;

@end

NS_ASSUME_NONNULL_END
105 changes: 105 additions & 0 deletions iosMath/render/MTMathListDisplay.m
Original file line number Diff line number Diff line change
Expand Up @@ -798,3 +798,108 @@ - (void)draw:(CGContextRef)context
CGContextRestoreGState(context);
}
@end

#pragma mark - MTInnerDisplay

@implementation MTInnerDisplay {
MTMathListDisplay *_inner;
}

- (instancetype) initWithInner:(MTMathListDisplay*) inner leftDelimiter:(MTDisplay*) leftDelimiter rightDelimiter:(MTDisplay*) rightDelimiter atIndex:(NSUInteger) index
{
self = [super init];
if (self) {
_leftDelimiter = leftDelimiter;
_rightDelimiter = rightDelimiter;
_inner = inner;
_index = index;
self.range = NSMakeRange(_index, 1);

self.width = leftDelimiter.width + inner.width + rightDelimiter.width;
}
return self;
}

- (void)setPosition:(CGPoint)position
{
super.position = position;
[self updateLeftDelimiterPosition];
[self updateInnerPosition];
[self updateRightDelimiterPosition];
}

- (void) updateLeftDelimiterPosition
{
if (_leftDelimiter) {
_leftDelimiter.position = self.position;
}
}

- (void) updateRightDelimiterPosition
{
if (_rightDelimiter) {
_rightDelimiter.position = CGPointMake(_inner.position.x + _inner.width, self.position.y);
}
}

- (void) updateInnerPosition
{
if (_leftDelimiter) {
_inner.position = CGPointMake(_leftDelimiter.position.x + _leftDelimiter.width, self.position.y);
} else {
_inner.position = self.position;
}
}

- (CGFloat)ascent
{
if (_leftDelimiter) {
return _leftDelimiter.ascent;
}
if (_rightDelimiter) {
return _rightDelimiter.ascent;
}
return _inner.ascent;
}

- (CGFloat)descent
{
if (_leftDelimiter) {
return _leftDelimiter.descent;
}
if (_rightDelimiter) {
return _rightDelimiter.descent;
}
return _inner.descent;
}

- (CGFloat)width
{
CGFloat w = _inner.width;
if (_leftDelimiter) {
w += _leftDelimiter.width;
}
if (_rightDelimiter) {
w += _rightDelimiter.width;
}
return w;
}

- (void)setTextColor:(MTColor *)textColor
{
[super setTextColor:textColor];
self.leftDelimiter.textColor = textColor;
self.rightDelimiter.textColor = textColor;
_inner.textColor = textColor;
}

- (void)draw:(CGContextRef)context
{
[super draw:context];
// Draw the elements.
[self.leftDelimiter draw:context];
[self.rightDelimiter draw:context];
[_inner draw:context];
}

@end
14 changes: 14 additions & 0 deletions iosMath/render/internal/MTMathListDisplayInternal.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,17 @@
- (instancetype)initWithAccent:(MTGlyphDisplay*) glyph accentee:(MTMathListDisplay*) accentee range:(NSRange) range NS_DESIGNATED_INITIALIZER;

@end


@interface MTInnerDisplay ()

- (instancetype) initWithInner:(MTMathListDisplay*) inner leftDelimiter:(MTDisplay*) leftDelimiter rightDelimiter:(MTDisplay*) rightDelimiter atIndex:(NSUInteger) index NS_DESIGNATED_INITIALIZER;

@property (nonatomic) MTMathListDisplay* inner;

@property (nonatomic, nullable) MTDisplay* leftDelimiter;
@property (nonatomic, nullable) MTDisplay* rightDelimiter;

@property (nonatomic) NSUInteger index;

@end
90 changes: 43 additions & 47 deletions iosMath/render/internal/MTTypesetter.m
Original file line number Diff line number Diff line change
Expand Up @@ -707,12 +707,7 @@ - (void) createDisplayAtoms:(NSArray*) preprocessed
}
[self addInterElementSpace:prevNode currentType:atom.type];
MTInner* inner = (MTInner*) atom;
MTDisplay* display = nil;
if (inner.leftBoundary || inner.rightBoundary) {
display = [self makeLeftRight:inner];
} else {
display = [MTTypesetter createLineForMathList:inner.innerList font:_font style:_style cramped:_cramped];
}
MTInnerDisplay* display = [self makeInner:inner atIndex:atom.indexRange.location];
display.position = _currentPosition;
_currentPosition.x += display.width;
[_displayAtoms addObject:display];
Expand Down Expand Up @@ -1507,47 +1502,6 @@ - (MTDisplay*) addLimitsToDisplay:(MTDisplay*) display forOperator:(MTLargeOpera

#pragma mark Large delimiters

// Delimiter shortfall from plain.tex
static const NSInteger kDelimiterFactor = 901;
static const NSInteger kDelimiterShortfallPoints = 5;

- (MTDisplay*) makeLeftRight:(MTInner*) inner
{
NSAssert(inner.leftBoundary || inner.rightBoundary, @"Inner should have a boundary to call this function");

MTMathListDisplay* innerListDisplay = [MTTypesetter createLineForMathList:inner.innerList font:_font style:_style cramped:_cramped spaced:YES];
CGFloat axisHeight = _styleFont.mathTable.axisHeight;
// delta is the max distance from the axis
CGFloat delta = MAX(innerListDisplay.ascent - axisHeight, innerListDisplay.descent + axisHeight);
CGFloat d1 = (delta / 500) * kDelimiterFactor; // This represents atleast 90% of the formula
CGFloat d2 = 2 * delta - kDelimiterShortfallPoints; // This represents a shortfall of 5pt
// The size of the delimiter glyph should cover at least 90% of the formula or
// be at most 5pt short.
CGFloat glyphHeight = MAX(d1, d2);

NSMutableArray* innerElements = [[NSMutableArray alloc] init];
CGPoint position = CGPointZero;
if (inner.leftBoundary && inner.leftBoundary.nucleus.length > 0) {
MTDisplay* leftGlyph = [self findGlyphForBoundary:inner.leftBoundary.nucleus withHeight:glyphHeight];
leftGlyph.position = position;
position.x += leftGlyph.width;
[innerElements addObject:leftGlyph];
}

innerListDisplay.position = position;
position.x += innerListDisplay.width;
[innerElements addObject:innerListDisplay];

if (inner.rightBoundary && inner.rightBoundary.nucleus.length > 0) {
MTDisplay* rightGlyph = [self findGlyphForBoundary:inner.rightBoundary.nucleus withHeight:glyphHeight];
rightGlyph.position = position;
position.x += rightGlyph.width;
[innerElements addObject:rightGlyph];
}
MTMathListDisplay* innerDisplay = [[MTMathListDisplay alloc] initWithDisplays:innerElements range:inner.indexRange];
return innerDisplay;
}

- (MTDisplay*) findGlyphForBoundary:(NSString*) delimiter withHeight:(CGFloat) glyphHeight
{
CGFloat glyphAscent, glyphDescent, glyphWidth;
Expand Down Expand Up @@ -1858,4 +1812,46 @@ - (void) positionRows:(NSArray<MTDisplay*>*) rows forTable:(MTMathTable*) table
row.position = CGPointMake(row.position.x, row.position.y - shiftDown);
}
}

#pragma mark inner

// Delimiter shortfall from plain.tex
static const NSInteger kDelimiterFactor = 901;
static const NSInteger kDelimiterShortfallPoints = 5;

- (MTInnerDisplay*) makeInner:(MTInner*) inner atIndex:(NSUInteger) index
{
NSAssert(inner.leftBoundary || inner.rightBoundary, @"Inner should have a boundary to call this function");

MTMathListDisplay* innerListDisplay = [MTTypesetter createLineForMathList:inner.innerList font:_font style:_style cramped:_cramped];
CGFloat axisHeight = _styleFont.mathTable.axisHeight;
// delta is the max distance from the axis
CGFloat delta = MAX(innerListDisplay.ascent - axisHeight, innerListDisplay.descent + axisHeight);
CGFloat d1 = (delta / 500) * kDelimiterFactor; // This represents atleast 90% of the formula
CGFloat d2 = 2 * delta - kDelimiterShortfallPoints; // This represents a shortfall of 5pt
// The size of the delimiter glyph should cover at least 90% of the formula or
// be at most 5pt short.
CGFloat glyphHeight = MAX(d1, d2);

MTDisplay* leftDelimiter = nil;
if (inner.leftBoundary && inner.leftBoundary.nucleus.length > 0) {
MTDisplay* leftGlyph = [self findGlyphForBoundary:inner.leftBoundary.nucleus withHeight:glyphHeight];
if (leftGlyph) {
leftDelimiter = leftGlyph;
}
}

MTDisplay* rightDelimiter = nil;
if (inner.rightBoundary && inner.rightBoundary.nucleus.length > 0) {
MTDisplay* rightGlyph = [self findGlyphForBoundary:inner.rightBoundary.nucleus withHeight:glyphHeight];
if (rightGlyph) {
rightDelimiter = rightGlyph;
}
}

MTInnerDisplay* innerDisplay = [[MTInnerDisplay alloc] initWithInner:innerListDisplay leftDelimiter:leftDelimiter rightDelimiter:rightDelimiter atIndex: index];

return innerDisplay;
}

@end
49 changes: 21 additions & 28 deletions iosMathTests/MTTypesetterTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -993,53 +993,46 @@ - (void)testInner {
XCTAssertEqual(display.subDisplays.count, 1);

MTDisplay* sub0 = display.subDisplays[0];
XCTAssertTrue([sub0 isKindOfClass:[MTMathListDisplay class]]);
MTMathListDisplay* display2 = (MTMathListDisplay*) sub0;
XCTAssertEqual(display2.type, kMTLinePositionRegular);
XCTAssertTrue([sub0 isKindOfClass:[MTInnerDisplay class]]);
MTInnerDisplay* display2 = (MTInnerDisplay*) sub0;
XCTAssertTrue(CGPointEqualToPoint(display2.position, CGPointZero));
XCTAssertTrue(NSEqualRanges(display2.range, NSMakeRange(0, 1)));
XCTAssertFalse(display2.hasScript);
XCTAssertEqual(display2.index, NSNotFound);
XCTAssertEqual(display2.subDisplays.count, 3);

MTDisplay* subLeft = display2.subDisplays[0];
XCTAssertTrue([subLeft isKindOfClass:[MTGlyphDisplay class]]);
MTGlyphDisplay* glyph = (MTGlyphDisplay*) subLeft;
XCTAssertTrue([display2.leftDelimiter isKindOfClass:[MTGlyphDisplay class]]);

MTGlyphDisplay* glyph = (MTGlyphDisplay*) display2.leftDelimiter;
XCTAssertTrue(CGPointEqualToPoint(glyph.position, CGPointZero));
XCTAssertTrue(NSEqualRanges(glyph.range, NSMakeRange(NSNotFound, 0)));
XCTAssertFalse(glyph.hasScript);

MTDisplay* sub3 = display2.subDisplays[1];
XCTAssertTrue([sub3 isKindOfClass:[MTMathListDisplay class]]);
MTMathListDisplay* display3 = (MTMathListDisplay*) sub3;
XCTAssertEqual(display3.type, kMTLinePositionRegular);
XCTAssertEqualsCGPoint(display3.position, CGPointMake(7.78, 0), 0.01);
XCTAssertTrue(NSEqualRanges(display3.range, NSMakeRange(0, 1)));
XCTAssertFalse(display3.hasScript);
XCTAssertEqual(display3.index, NSNotFound);
XCTAssertEqual(display3.subDisplays.count, 1);

MTDisplay* subsub3 = display3.subDisplays[0];

XCTAssertTrue([display2.inner isKindOfClass:[MTMathListDisplay class]]);
MTMathListDisplay* innerMathListDisplay = (MTMathListDisplay*) display2.inner;
XCTAssertEqualsCGPoint(innerMathListDisplay.position, CGPointMake(7.78, 0), 0.001);
XCTAssertTrue(NSEqualRanges(innerMathListDisplay.range, NSMakeRange(0, 1)));
XCTAssertFalse(innerMathListDisplay.hasScript);
XCTAssertEqual(innerMathListDisplay.index, NSNotFound);
XCTAssertEqual(innerMathListDisplay.subDisplays.count, 1);

MTDisplay* subsub3 = innerMathListDisplay.subDisplays[0];
XCTAssertTrue([subsub3 isKindOfClass:[MTCTLineDisplay class]]);
MTCTLineDisplay* line = (MTCTLineDisplay*) subsub3;
XCTAssertEqual(line.atoms.count, 1);
// The x is italicized
XCTAssertEqualObjects(line.attributedString.string, @"𝑥");
XCTAssertTrue(CGPointEqualToPoint(line.position, CGPointZero));
XCTAssertFalse(line.hasScript);

MTDisplay* subRight = display2.subDisplays[2];
XCTAssertTrue([subRight isKindOfClass:[MTGlyphDisplay class]]);
MTGlyphDisplay* glyph2 = (MTGlyphDisplay*) subRight;
XCTAssertEqualsCGPoint(glyph2.position, CGPointMake(19.22, 0), 0.01);

XCTAssertTrue([display2.rightDelimiter isKindOfClass:[MTGlyphDisplay class]]);
MTGlyphDisplay* glyph2 = (MTGlyphDisplay*) display2.rightDelimiter;
XCTAssertEqualsCGPoint(glyph2.position, CGPointMake(19.22, 0), 0.001);
XCTAssertTrue(NSEqualRanges(glyph2.range, NSMakeRange(NSNotFound, 0)), "Got %@ instead", NSStringFromRange(glyph2.range));
XCTAssertFalse(glyph2.hasScript);

// dimensions
XCTAssertEqual(display.ascent, display2.ascent);
XCTAssertEqual(display.descent, display2.descent);
XCTAssertEqual(display.width, display2.width);

XCTAssertEqualWithAccuracy(display.ascent, 14.97, 0.001);
XCTAssertEqualWithAccuracy(display.descent, 4.97, 0.001);
XCTAssertEqualWithAccuracy(display.width, 27, 0.01);
Expand Down