diff --git a/darwin/attrstr.m b/darwin/attrstr.m
index fd45ec25..86039fad 100644
--- a/darwin/attrstr.m
+++ b/darwin/attrstr.m
@@ -403,8 +403,13 @@ static CTParagraphStyleRef mkParagraphStyle(uiDrawTextLayoutParams *p)
 	return ps;
 }
 
-CFAttributedStringRef attrstrToCoreFoundation(uiDrawTextLayoutParams *p, NSArray **backgroundBlocks)
+static const UniChar emptyChars[] = { 0x20, 0x0 };
+static const CFIndex emptyCharCount = 1;
+
+CFAttributedStringRef attrstrToCoreFoundation(uiDrawTextLayoutParams *p, BOOL *isEmpty, NSArray **backgroundBlocks)
 {
+	const UniChar *chars;
+	CFIndex charCount;
 	CFStringRef cfstr;
 	CFMutableDictionaryRef defaultAttrs;
 	CTFontRef defaultCTFont;
@@ -413,7 +418,15 @@ CFAttributedStringRef attrstrToCoreFoundation(uiDrawTextLayoutParams *p, NSArray
 	CFMutableAttributedStringRef mas;
 	struct foreachParams fep;
 
-	cfstr = CFStringCreateWithCharacters(NULL, attrstrUTF16(p->String), attrstrUTF16Len(p->String));
+	*isEmpty = NO;
+	chars = attrstrUTF16(p->String);
+	charCount = attrstrUTF16Len(p->String);
+	if (charCount == 0) {
+		*isEmpty = YES;
+		chars = emptyChars;
+		charCount = emptyCharCount;
+	}
+	cfstr = CFStringCreateWithCharacters(NULL, chars, charCount);
 	if (cfstr == NULL) {
 		// TODO
 	}
diff --git a/darwin/drawtext.m b/darwin/drawtext.m
index 1fa5920e..65912383 100644
--- a/darwin/drawtext.m
+++ b/darwin/drawtext.m
@@ -2,13 +2,16 @@
 #import "uipriv_darwin.h"
 #import "draw.h"
 
-// TODO on an empty string nLines == 0
-// we must prevent this somehow
-// TODO in general, every function could be more robust, but we cannot have a situation where there are zero lines
+// TODO in general, every function could be more robust
 // TODO what happens to extents if only whitespace?
+// TODO for empty layouts:
+// - check if alignment correct compared to other OSs, or expected behavior at all
+// - double-check if uiAttributedString allows zero-length attributes; I forget if I did
 
 struct uiDrawTextLayout {
 	CFAttributedStringRef attrstr;
+	// this is needed because Core Text will give us an empty line array on a frame made with an empty string
+	BOOL isEmpty;
 
 	// the width as passed into uiDrawTextLayout constructors
 	double width;
@@ -41,7 +44,7 @@
 };
 
 // TODO document that lines may or may not overlap because ours do in the case of multiple combining characters
-static uiDrawTextLayoutLineMetrics *computeLineMetrics(CTFrameRef frame, CGSize size)
+static uiDrawTextLayoutLineMetrics *computeLineMetrics(CTFrameRef frame, CGSize size, BOOL isEmpty)
 {
 	uiDrawTextLayoutLineMetrics *metrics;
 	CFArrayRef lines;
@@ -79,6 +82,8 @@
 		metrics[i].X = origins[i].x;
 		metrics[i].Y = origins[i].y - descent - leading;
 		metrics[i].Width = bounds.size.width;
+		if (isEmpty)
+			metrics[i].Width = 0;
 		metrics[i].Height = ascent + descent + leading;
 
 		metrics[i].BaselineY = origins[i].y;
@@ -117,7 +122,7 @@
 	CGRect rect;
 
 	tl = uiNew(uiDrawTextLayout);
-	tl->attrstr = attrstrToCoreFoundation(p, &(tl->backgroundBlocks));
+	tl->attrstr = attrstrToCoreFoundation(p, &(tl->isEmpty), &(tl->backgroundBlocks));
 	range.location = 0;
 	range.length = CFAttributedStringGetLength(tl->attrstr);
 	tl->width = p->Width;
@@ -152,7 +157,7 @@
 
 	tl->lines = CTFrameGetLines(tl->frame);
 	tl->nLines = CFArrayGetCount(tl->lines);
-	tl->lineMetrics = computeLineMetrics(tl->frame, tl->size);
+	tl->lineMetrics = computeLineMetrics(tl->frame, tl->size, tl->isEmpty);
 
 	// and finally copy the UTF-8/UTF-16 conversion tables
 	tl->u8tou16 = attrstrCopyUTF8ToUTF16(p->String, &(tl->nUTF8));
@@ -180,6 +185,9 @@ void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)
 	backgroundBlock b;
 	CGAffineTransform textMatrix;
 
+	if (tl->isEmpty)
+		return;
+
 	CGContextSaveGState(c->c);
 	// save the text matrix because it's not part of the graphics state
 	textMatrix = CGContextGetTextMatrix(c->c);
@@ -216,6 +224,8 @@ void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)
 void uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height)
 {
 	*width = tl->size.width;
+	if (tl->isEmpty)
+		*width = 0;
 	*height = tl->size.height;
 }
 
@@ -233,6 +243,8 @@ void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start
 	range = CTLineGetStringRange(lr);
 	*start = tl->u16tou8[range.location];
 	*end = tl->u16tou8[range.location + range.length];
+	if (tl->isEmpty)
+		*start = *end;
 }
 
 void uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m)
@@ -262,14 +274,17 @@ void uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, size_t *p
 	*line = i;
 
 	ln = (CTLineRef) CFArrayGetValueAtIndex(tl->lines, i);
-	// note: according to the docs, we pass a y of 0 for this since the is the baseline of that line (the point is relative to the line)
-	// note: x is relative to the line origin
 	x -= tl->lineMetrics[*line].X;
-	p = CTLineGetStringIndexForPosition(ln, CGPointMake(x, 0));
-	if (p == kCFNotFound) {
-		// TODO
+	*pos = 0;
+	if (!tl->isEmpty) {
+		// note: according to the docs, we pass a y of 0 for this since the is the baseline of that line (the point is relative to the line)
+		// note: x is relative to the line origin
+		p = CTLineGetStringIndexForPosition(ln, CGPointMake(x, 0));
+		if (p == kCFNotFound) {
+			// TODO
+		}
+		*pos = tl->u16tou8[p];
 	}
-	*pos = tl->u16tou8[p];
 }
 
 double uiDrawTextLayoutByteLocationInLine(uiDrawTextLayout *tl, size_t pos, int line)
@@ -282,6 +297,9 @@ void uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, size_t *p
 		return -1;
 	lr = (CTLineRef) CFArrayGetValueAtIndex(tl->lines, line);
 	range = CTLineGetStringRange(lr);
+	// TODO is the behavior of this part correct?
+	if (tl->isEmpty)
+		range.length = 0;
 	// note: >, not >=, because the position at end is valid!
 	if (pos < range.location || pos > (range.location + range.length))
 		return -1;
diff --git a/darwin/uipriv_darwin.h b/darwin/uipriv_darwin.h
index 0303c32c..d44ee410 100644
--- a/darwin/uipriv_darwin.h
+++ b/darwin/uipriv_darwin.h
@@ -151,7 +151,7 @@ extern void fontdescFromCTFontDescriptor(CTFontDescriptorRef ctdesc, uiDrawFontD
 extern void initUnderlineColors(void);
 extern void uninitUnderlineColors(void);
 typedef void (^backgroundBlock)(uiDrawContext *c, uiDrawTextLayout *layout, double x, double y);
-extern CFAttributedStringRef attrstrToCoreFoundation(uiDrawTextLayoutParams *p, NSArray **backgroundBlocks);
+extern CFAttributedStringRef attrstrToCoreFoundation(uiDrawTextLayoutParams *p, BOOL *isEmpty, NSArray **backgroundBlocks);
 
 // aat.m
 typedef void (^aatBlock)(uint16_t type, uint16_t selector);