Browse Source

Merge pull request #37 from jeremieroy/master

Refactor of fontsdf sample to show a big scrollable, scalable, rotable text
Branimir Karadžić 12 years ago
parent
commit
0d76b652a9

+ 6 - 0
README.md

@@ -340,6 +340,12 @@ http://fontfabric.com/signika-font/
 Visitor Font  
 http://www.dafont.com/visitor.font
 
+Special-Elite Font
+http://www.fontspace.com/astigmatic-one-eye-typographic-institute/special-elite
+
+Sherlock Holmes text
+http://www.gutenberg.org/ebooks/1661
+
 Tree Pack 1  
 http://www.turbosquid.com/3d-models/free-obj-mode-tree-pack/506851
 

+ 167 - 47
examples/11-fontsdf/fontsdf.cpp

@@ -4,7 +4,6 @@
  */
 
 #include "../common/common.h"
-
 #include <bgfx.h>
 #include <bx/timer.h>
 #include "../common/entry.h"
@@ -13,7 +12,12 @@
 #include "../common/processevents.h"
 
 #include "../common/font/font_manager.h"
+#include "../common/font/text_metrics.h"
 #include "../common/font/text_buffer_manager.h"
+#include "../common/imgui/imgui.h"
+
+#include <stdio.h>
+#include <string.h>
 
 inline void mtxTranslate(float* _result, float x, float y, float z)
 {
@@ -33,6 +37,56 @@ inline void mtxScale(float* _result, float x, float y, float z)
 	_result[15] = 1.0f;
 }
 
+long int fsize(FILE* _file)
+{
+	long int pos = ftell(_file);
+	fseek(_file, 0L, SEEK_END);
+	long int size = ftell(_file);
+	fseek(_file, pos, SEEK_SET);
+	return size;
+}
+
+char* loadText(const char* _textFile)
+{	
+	FILE* pFile;
+	pFile = fopen(_textFile, "rb");
+	if (pFile == NULL)
+	{		
+		return NULL;
+	}
+
+	// Go to the end of the file.
+	if (fseek(pFile, 0L, SEEK_END) == 0)
+	{
+		// Get the size of the file.
+		long bufsize = ftell(pFile);
+		if (bufsize == -1)
+		{
+			fclose(pFile);			
+			return NULL;
+		}
+
+		char* buffer = new char[bufsize];
+
+		// Go back to the start of the file.
+		fseek(pFile, 0L, SEEK_SET);
+
+		// Read the entire file into memory.
+		uint32_t newLen = fread( (void*)buffer, sizeof(char), bufsize, pFile);
+		if (newLen == 0)
+		{
+			fclose(pFile);
+			delete[] buffer;
+			return NULL;
+		}
+
+		fclose(pFile);
+
+		return buffer;
+	}
+	return NULL;
+}
+
 int _main_(int /*_argc*/, char** /*_argv*/)
 {
 	uint32_t width = 1280;
@@ -55,35 +109,94 @@ int _main_(int /*_argc*/, char** /*_argv*/)
 		, 0
 		);
 
-	// Init the text rendering system.
-	FontManager* fontManager = new FontManager(512);
-	TextBufferManager* textBufferManager = new TextBufferManager(fontManager);
-
-	TrueTypeHandle times_tt = fontManager->loadTrueTypeFromFile("font/bleeding_cowboys.ttf");
-
-	// Create a distance field font.
-	FontHandle distance_font = fontManager->createFontByPixelSize(times_tt, 0, 48, FONT_TYPE_DISTANCE);
+	FILE* file = fopen("font/droidsans.ttf", "rb");
+	uint32_t size = (uint32_t)fsize(file);
+	void* data = malloc(size);
+	size_t ignore = fread(data, 1, size, file);
+	BX_UNUSED(ignore);
+	fclose(file);
 
-	// Create a scalled down version of the same font (without adding 
-	// anything to the atlas).
-	FontHandle smaller_font = fontManager->createScaledFontToPixelSize(distance_font, 32);
+	imguiCreate(data, size);
 
-	// Preload glyph and generate (generate bitmap's).
-	fontManager->preloadGlyph(distance_font, L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.,\" \n");
+	free(data);
 
-	// You can unload the TTF files at this stage, but in that case, the 
-	// set of glyph's will be limited to the set of preloaded glyph.
-	fontManager->unloadTrueType(times_tt);
+	char* bigText = loadText( "text/sherlock_holmes_a_scandal_in_bohemia_arthur_conan_doyle.txt");
 
-	TextBufferHandle staticText = textBufferManager->createTextBuffer(FONT_TYPE_DISTANCE, STATIC);
-	textBufferManager->setTextColor(staticText, 0xDD0000FF);
+	// Init the text rendering system.
+	FontManager* fontManager = new FontManager(512);
+	TextBufferManager* textBufferManager = new TextBufferManager(fontManager);
 
-	textBufferManager->appendText(staticText, distance_font, L"BGFX ");
-	textBufferManager->appendText(staticText, smaller_font, L"bgfx");
+	TrueTypeHandle font_tt = fontManager->loadTrueTypeFromFile("font/special_elite.ttf");//bleeding_cowboys.ttf");
 
-	int64_t timeOffset = bx::getHPCounter();
-	while (!processEvents(width, height, debug, reset) )
+	// Create a distance field font.
+	FontHandle base_distance_font = fontManager->createFontByPixelSize(font_tt, 0, 48, FONT_TYPE_DISTANCE);
+	
+	// Create a scaled down version of the same font (without adding anything to the atlas).
+	FontHandle scaled_font = fontManager->createScaledFontToPixelSize(base_distance_font, 14);
+		
+	TextLineMetrics metrics(fontManager, scaled_font);		
+	uint32_t lineCount = metrics.getLineCount(bigText);
+	
+	float visibleLineCount = 20.0f;
+
+	const char* textBegin = 0;
+	const char* textEnd = 0;
+	metrics.getSubText(bigText, 0, (uint32_t)visibleLineCount, textBegin, textEnd);
+
+	TextBufferHandle scrollableBuffer = textBufferManager->createTextBuffer(FONT_TYPE_DISTANCE, TRANSIENT);
+	textBufferManager->setTextColor(scrollableBuffer, 0xFFFFFFFF);
+
+	textBufferManager->appendText(scrollableBuffer, scaled_font, textBegin, textEnd);
+	
+	MouseState mouseState;
+	int32_t scrollArea = 0;
+	while (!processEvents(width, height, debug, reset, &mouseState) )
 	{
+		imguiBeginFrame(mouseState.m_mx
+			, mouseState.m_my
+			, (mouseState.m_buttons[entry::MouseButton::Left  ] ? IMGUI_MBUT_LEFT  : 0)
+			| (mouseState.m_buttons[entry::MouseButton::Right ] ? IMGUI_MBUT_RIGHT : 0)
+			, 0
+			, width
+			, height
+			);
+
+		const int guiPanelWidth = 250;
+		const int guiPanelHeight = 200;
+	
+		imguiBeginScrollArea("Text Area", width - guiPanelWidth - 10, 10, guiPanelWidth, guiPanelHeight, &scrollArea);
+		imguiSeparatorLine();
+				
+		static float textScroll = 0.0f;
+		static float textRotation = 0.0f;
+		static float textScale = 1.0f;
+		static float textSize = 14.0f;
+		
+		bool recomputeVisibleText = false;		
+		recomputeVisibleText |= imguiSlider("Number of lines", &visibleLineCount, 1.0f, 177.0f , 1.0f);
+		if(imguiSlider("Font size", &textSize, 6.0f, 64.0f , 1.0f))
+		{
+			fontManager->destroyFont(scaled_font);
+			scaled_font = fontManager->createScaledFontToPixelSize(base_distance_font, (uint32_t) textSize);
+			metrics = TextLineMetrics (fontManager, scaled_font);			
+			recomputeVisibleText = true;
+		}
+
+		recomputeVisibleText |= imguiSlider("Scroll", &textScroll, 0.0f, (lineCount-visibleLineCount) , 1.0f);
+		imguiSlider("Rotate", &textRotation, 0.0f, (float) M_PI *2.0f , 0.1f);
+		recomputeVisibleText |= imguiSlider("Scale", &textScale, 0.1f, 10.0f , 0.1f);
+
+		if(	recomputeVisibleText)
+		{
+			textBufferManager->clearTextBuffer(scrollableBuffer);
+			metrics.getSubText(bigText,(uint32_t)textScroll, (uint32_t)(textScroll+visibleLineCount), textBegin, textEnd);			
+			textBufferManager->appendText(scrollableBuffer, scaled_font, textBegin, textEnd);
+		}			
+		
+		imguiEndScrollArea();
+		
+		imguiEndFrame();
+
 		// Set view 0 default viewport.
 		bgfx::setViewRect(0, 0, 0, width, height);
 
@@ -97,7 +210,6 @@ int _main_(int /*_argc*/, char** /*_argv*/)
 		last = now;
 		const double freq = double(bx::getHPFrequency() );
 		const double toMs = 1000.0 / freq;
-		float time = (float)( (now - timeOffset) / double(bx::getHPFrequency() ) );
 
 		// Use debug font to print information about this example.
 		bgfx::dbgTextClear();
@@ -115,42 +227,50 @@ int _main_(int /*_argc*/, char** /*_argv*/)
 
 		// Setup a top-left ortho matrix for screen space drawing.
 		mtxOrtho(proj, centering, width + centering, height + centering, centering, -1.0f, 1.0f);
-
+				
 		// Set view and projection matrix for view 0.
 		bgfx::setViewTransform(0, view, proj);
-		TextRectangle rect = textBufferManager->getRectangle(staticText);
-
-		float mtxA[16];
-		float mtxB[16];
-		float mtxC[16];
-		mtxRotateZ(mtxA, time * 0.37f);
-		mtxTranslate(mtxB, -(rect.width * 0.5f), -(rect.height * 0.5f), 0);
-
-		mtxMul(mtxC, mtxB, mtxA);
-
-		float scale = 4.1f + 4.0f * sinf(time);
-		mtxScale(mtxA, scale, scale, 1.0f);
-		mtxMul(mtxB, mtxC, mtxA);
-
-		mtxTranslate(mtxC, ( (width) * 0.5f), ( (height) * 0.5f), 0);
-		mtxMul(mtxA, mtxB, mtxC);
+		
+		//very crude approximation :(
+		float textAreaWidth = 0.5f * 66.0f * fontManager->getFontInfo(scaled_font).maxAdvanceWidth;
+
+		float textRotMat[16];
+		float textCenterMat[16];
+		float textScaleMat[16];
+		float screenCenterMat[16];
+		
+		mtxRotateZ(textRotMat, textRotation);
+		mtxTranslate(textCenterMat, -(textAreaWidth * 0.5f), (-visibleLineCount)*metrics.getLineHeight()*0.5f, 0);
+		mtxScale(textScaleMat, textScale, textScale, 1.0f);
+		mtxTranslate(screenCenterMat, ( (width) * 0.5f), ( (height) * 0.5f), 0);
+
+		//first translate to text center, then scale, then rotate
+		float tmpMat[16];
+		mtxMul(tmpMat, textCenterMat, textRotMat);
+		
+		float tmpMat2[16];
+		mtxMul(tmpMat2, tmpMat, textScaleMat);
+		
+		float tmpMat3[16];
+		mtxMul(tmpMat3, tmpMat2, screenCenterMat);
 
 		// Set model matrix for rendering.
-		bgfx::setTransform(mtxA);
-
+		bgfx::setTransform(tmpMat3);
+	
 		// Draw your text.
-		textBufferManager->submitTextBuffer(staticText, 0);
+		textBufferManager->submitTextBuffer(scrollableBuffer, 0);
 
 		// Advance to next frame. Rendering thread will be kicked to
 		// process submitted rendering primitives.
 		bgfx::frame();
 	}
 
+	fontManager->unloadTrueType(font_tt);
 	// Destroy the fonts.
-	fontManager->destroyFont(distance_font);
-	fontManager->destroyFont(smaller_font);
+	fontManager->destroyFont(base_distance_font);
+	fontManager->destroyFont(scaled_font);
 
-	textBufferManager->destroyTextBuffer(staticText);
+	textBufferManager->destroyTextBuffer(scrollableBuffer);
 
 	delete textBufferManager;
 	delete fontManager;

+ 3 - 1
examples/common/font/font_manager.cpp

@@ -165,6 +165,7 @@ FontInfo TrueTypeFont::getFontInfo()
 	outFontInfo.ascender = metrics.ascender / 64.0f;
 	outFontInfo.descender = metrics.descender / 64.0f;
 	outFontInfo.lineGap = (metrics.height - metrics.ascender + metrics.descender) / 64.0f;
+	outFontInfo.maxAdvanceWidth = metrics.max_advance/ 64.0f;
 
 	outFontInfo.underlinePosition = FT_MulFix(holder->face->underline_position, metrics.y_scale) / 64.0f;
 	outFontInfo.underlineThickness = FT_MulFix(holder->face->underline_thickness, metrics.y_scale) / 64.0f;
@@ -647,6 +648,7 @@ FontHandle FontManager::createScaledFontToPixelSize(FontHandle _baseFontHandle,
 	newFontInfo.ascender = (newFontInfo.ascender * newFontInfo.scale);
 	newFontInfo.descender = (newFontInfo.descender * newFontInfo.scale);
 	newFontInfo.lineGap = (newFontInfo.lineGap * newFontInfo.scale);
+	newFontInfo.maxAdvanceWidth = (newFontInfo.maxAdvanceWidth * newFontInfo.scale);
 	newFontInfo.underlineThickness = (newFontInfo.underlineThickness * newFontInfo.scale);
 	newFontInfo.underlinePosition = (newFontInfo.underlinePosition * newFontInfo.scale);
 
@@ -782,7 +784,7 @@ bool FontManager::preloadGlyph(FontHandle _handle, CodePoint _codePoint)
 	return false;
 }
 
-const FontInfo& FontManager::getFontInfo(FontHandle _handle)
+const FontInfo& FontManager::getFontInfo(FontHandle _handle) const
 {
 	BX_CHECK(bgfx::invalidHandle != _handle.idx, "Invalid handle used");
 	return m_cachedFonts[_handle.idx].fontInfo;

+ 4 - 1
examples/common/font/font_manager.h

@@ -7,6 +7,7 @@
 #define __FONT_MANAGER_H__
 
 #include <bx/handlealloc.h>
+#include <bgfx.h>
 
 class Atlas;
 
@@ -29,6 +30,8 @@ struct FontInfo
 	float descender;
 	/// The spacing in pixels between one row's descent and the next row's ascent.
 	float lineGap;
+	/// This field gives the maximum horizontal cursor advance for all glyphs in the font. 
+	float maxAdvanceWidth;
 	/// The thickness of the under/hover/strike-trough line in pixels.
 	float underlineThickness;
 	/// The position of the underline relatively to the baseline.
@@ -169,7 +172,7 @@ public:
 	/// Return the font descriptor of a font.
 	///
 	/// @remark the handle is required to be valid
-	const FontInfo& getFontInfo(FontHandle _handle);
+	const FontInfo& getFontInfo(FontHandle _handle) const;
 
 	/// Return the rendering informations about the glyph region. Load the 
 	/// glyph from a TrueType font if possible

+ 60 - 48
examples/common/font/text_buffer_manager.cpp

@@ -22,7 +22,7 @@
 #include "fs_font_distance_field_subpixel.bin.h"
 
 #define MAX_TEXT_BUFFER_COUNT   64
-#define MAX_BUFFERED_CHARACTERS 8192
+#define MAX_BUFFERED_CHARACTERS (8192 - 5)
 
 class TextBuffer
 {
@@ -64,15 +64,11 @@ public:
 		m_penX = _x; m_penY = _y;
 	}
 
-	/// return the size of the text
-	//Rectangle measureText(FontHandle _fontHandle, const char * _string);
-	//Rectangle measureText(FontHandle _fontHandle, const wchar_t * _string);
-
 	/// append an ASCII/utf-8 string to the buffer using current pen position and color
-	void appendText(FontHandle _fontHandle, const char* _string);
+	void appendText(FontHandle _fontHandle, const char* _string, const char* _end = NULL);
 
 	/// append a wide char unicode string to the buffer using current pen position and color
-	void appendText(FontHandle _fontHandle, const wchar_t* _string);
+	void appendText(FontHandle _fontHandle, const wchar_t* _string, const wchar_t* _end = NULL);
 	
 	/// append a whole face of the atlas cube, mostly used for debugging and visualizing atlas
 	void appendAtlasFace(uint16_t _faceIndex);
@@ -215,7 +211,7 @@ TextBuffer::~TextBuffer()
 	delete[] m_indexBuffer;
 }
 
-void TextBuffer::appendText(FontHandle _fontHandle, const char* _string)
+void TextBuffer::appendText(FontHandle _fontHandle, const char* _string, const char* _end)
 {
 	GlyphInfo glyph;
 	const FontInfo& font = m_fontManager->getFontInfo(_fontHandle);
@@ -224,16 +220,17 @@ void TextBuffer::appendText(FontHandle _fontHandle, const char* _string)
 	{
 		m_originX = m_penX;
 		m_originY = m_penY;
-		m_lineDescender = 0; // font.m_descender;
-		m_lineAscender = 0; //font.m_ascender;
+		m_lineDescender = 0;
+		m_lineAscender = 0;
+		m_lineGap = 0;
 	}
 
 	CodePoint codepoint = 0;
 	uint32_t state = 0;
 
-	for (; *_string; ++_string)
+	for (; *_string && _string<_end ; ++_string)
 	{
-		if (!utf8_decode(&state, (uint32_t*)&codepoint, *_string) )
+		if (utf8_decode(&state, (uint32_t*)&codepoint, *_string) == UTF8_ACCEPT )
 		{
 			if (m_fontManager->getGlyphInfo(_fontHandle, codepoint, glyph) )
 			{
@@ -246,14 +243,10 @@ void TextBuffer::appendText(FontHandle _fontHandle, const char* _string)
 		}
 	}
 
-	if (state != UTF8_ACCEPT)
-	{
-		//	assert(false && "The string is not well-formed");
-		return; //"The string is not well-formed\n"
-	}
+	BX_CHECK(state == UTF8_ACCEPT, "The string is not well-formed");	
 }
 
-void TextBuffer::appendText(FontHandle _fontHandle, const wchar_t* _string)
+void TextBuffer::appendText(FontHandle _fontHandle, const wchar_t* _string, const wchar_t* _end)
 {
 	GlyphInfo glyph;
 	const FontInfo& font = m_fontManager->getFontInfo(_fontHandle);
@@ -262,14 +255,19 @@ void TextBuffer::appendText(FontHandle _fontHandle, const wchar_t* _string)
 	{
 		m_originX = m_penX;
 		m_originY = m_penY;
-		m_lineDescender = 0; // font.m_descender;
-		m_lineAscender = 0; //font.m_ascender;
+		m_lineDescender = 0;
+		m_lineAscender = 0;
 		m_lineGap = 0;
 	}
+	if( _end == NULL)
+	{
+		_end = _string + (uint32_t) wcslen(_string);
+	}
+	BX_CHECK(_end >= _string);
 
-	for (uint32_t ii = 0, end = (uint32_t)wcslen(_string); ii < end; ++ii)
+	for (const wchar_t* _current = _string; _current < _end; ++_current)
 	{
-		uint32_t _codePoint = _string[ii];
+		uint32_t _codePoint = *_current;
 		if (m_fontManager->getGlyphInfo(_fontHandle, _codePoint, glyph) )
 		{
 			appendGlyph(_codePoint, font, glyph);
@@ -280,8 +278,14 @@ void TextBuffer::appendText(FontHandle _fontHandle, const wchar_t* _string)
 		}
 	}
 }
+
 void TextBuffer::appendAtlasFace(uint16_t _faceIndex)
 {
+	if( m_vertexCount/4 >= MAX_BUFFERED_CHARACTERS)
+	{
+		return;
+	}
+
 	float x0 = m_penX;
 	float y0 = m_penY;
 	float x1 = x0 + (float)m_fontManager->getAtlas()->getTextureSize();
@@ -306,29 +310,40 @@ void TextBuffer::appendAtlasFace(uint16_t _faceIndex)
 
 void TextBuffer::clearTextBuffer()
 {
+	m_penX = 0;
+	m_penY = 0;
+	m_originX = 0;
+	m_originY = 0;
+
 	m_vertexCount = 0;
 	m_indexCount = 0;
 	m_lineStartIndex = 0;
 	m_lineAscender = 0;
 	m_lineDescender = 0;
+	m_lineGap = 0;
 	m_rectangle.width = 0;
 	m_rectangle.height = 0;
 }
 
 void TextBuffer::appendGlyph(CodePoint _codePoint, const FontInfo& _font, const GlyphInfo& _glyphInfo)
-{
+{	
+	if( m_vertexCount/4 >= MAX_BUFFERED_CHARACTERS)
+	{
+		return;
+	}
+
 	if (_codePoint == L'\n')
 	{
 		m_penX = m_originX;
-		m_penY -= m_lineDescender;
-		m_penY += m_lineGap;
-		m_lineDescender = 0;
-		m_lineAscender = 0;
+		m_penY += m_lineGap + m_lineAscender -m_lineDescender;
+		m_lineGap = _font.lineGap;
+		m_lineDescender = _font.descender;
+		m_lineAscender = _font.ascender;
 		m_lineStartIndex = m_vertexCount;
-
 		return;
 	}
 
+	//is there a change of font size that require the text on the left to be centered again ?
 	if (_font.ascender > m_lineAscender
 		|| (_font.descender < m_lineDescender) )
 	{
@@ -340,14 +355,12 @@ void TextBuffer::appendGlyph(CodePoint _codePoint, const FontInfo& _font, const
 
 		float txtDecals = (_font.ascender - m_lineAscender);
 		m_lineAscender = _font.ascender;
-		m_lineGap = _font.lineGap;
-
-		m_penY += txtDecals;
-		verticalCenterLastLine( (txtDecals), (m_penY - m_lineAscender), (m_penY - m_lineDescender + m_lineGap) );
+		m_lineGap = _font.lineGap;		
+		verticalCenterLastLine( (txtDecals), (m_penY - m_lineAscender), (m_penY + m_lineAscender - m_lineDescender + m_lineGap) );
 	}
 
-	float kerning = 0;
-	m_penX += kerning * _font.scale;
+	float kerning = 0 * _font.scale;
+	m_penX += kerning;
 
 	GlyphInfo& blackGlyph = m_fontManager->getBlackGlyph();
 
@@ -355,9 +368,9 @@ void TextBuffer::appendGlyph(CodePoint _codePoint, const FontInfo& _font, const
 	&&  m_backgroundColor & 0xFF000000)
 	{
 		float x0 = (m_penX - kerning);
-		float y0 = (m_penY - m_lineAscender);
+		float y0 = (m_penY);
 		float x1 = ( (float)x0 + (_glyphInfo.advance_x) );
-		float y1 = (m_penY - m_lineDescender + m_lineGap);
+		float y1 = (m_penY + m_lineAscender - m_lineDescender + m_lineGap);
 
 		m_fontManager->getAtlas()->packUV(blackGlyph.regionIndex, (uint8_t*)m_vertexBuffer, sizeof(TextVertex) * m_vertexCount + offsetof(TextVertex, u), sizeof(TextVertex) );
 
@@ -380,7 +393,7 @@ void TextBuffer::appendGlyph(CodePoint _codePoint, const FontInfo& _font, const
 	&&  m_underlineColor & 0xFF000000)
 	{
 		float x0 = (m_penX - kerning);
-		float y0 = (m_penY - m_lineDescender / 2);
+		float y0 = (m_penY + m_lineAscender - m_lineDescender * 0.5f);
 		float x1 = ( (float)x0 + (_glyphInfo.advance_x) );
 		float y1 = y0 + _font.underlineThickness;
 
@@ -405,7 +418,7 @@ void TextBuffer::appendGlyph(CodePoint _codePoint, const FontInfo& _font, const
 	&&  m_overlineColor & 0xFF000000)
 	{
 		float x0 = (m_penX - kerning);
-		float y0 = (m_penY - _font.ascender);
+		float y0 = (m_penY);
 		float x1 = ( (float)x0 + (_glyphInfo.advance_x) );
 		float y1 = y0 + _font.underlineThickness;
 
@@ -430,7 +443,7 @@ void TextBuffer::appendGlyph(CodePoint _codePoint, const FontInfo& _font, const
 	&&  m_strikeThroughColor & 0xFF000000)
 	{
 		float x0 = (m_penX - kerning);
-		float y0 = (m_penY - _font.ascender / 3);
+		float y0 = (m_penY + 0.666667f * _font.ascender);
 		float x1 = ( (float)x0 + (_glyphInfo.advance_x) );
 		float y1 = y0 + _font.underlineThickness;
 
@@ -451,9 +464,8 @@ void TextBuffer::appendGlyph(CodePoint _codePoint, const FontInfo& _font, const
 		m_indexCount += 6;
 	}
 
-	float x0_precise = m_penX + (_glyphInfo.offset_x);
-	float x0 = (x0_precise);
-	float y0 = (m_penY + (_glyphInfo.offset_y) );
+	float x0 = m_penX + (_glyphInfo.offset_x);
+	float y0 = (m_penY + m_lineAscender + (_glyphInfo.offset_y) );
 	float x1 = (x0 + _glyphInfo.width);
 	float y1 = (y0 + _glyphInfo.height);
 
@@ -479,9 +491,9 @@ void TextBuffer::appendGlyph(CodePoint _codePoint, const FontInfo& _font, const
 		m_rectangle.width = m_penX;
 	}
 
-	if ( (m_penY - m_lineDescender) > m_rectangle.height)
+	if ( (m_penY +m_lineAscender - m_lineDescender+m_lineGap) > m_rectangle.height)
 	{
-		m_rectangle.height = (m_penY - m_lineDescender);
+		m_rectangle.height = (m_penY +m_lineAscender - m_lineDescender+m_lineGap);
 	}
 }
 
@@ -813,18 +825,18 @@ void TextBufferManager::setPenPosition(TextBufferHandle _handle, float _x, float
 	bc.textBuffer->setPenPosition(_x, _y);
 }
 
-void TextBufferManager::appendText(TextBufferHandle _handle, FontHandle _fontHandle, const char* _string)
+void TextBufferManager::appendText(TextBufferHandle _handle, FontHandle _fontHandle, const char* _string, const char* _end)
 {
 	BX_CHECK(bgfx::invalidHandle != _handle.idx, "Invalid handle used");
 	BufferCache& bc = m_textBuffers[_handle.idx];
-	bc.textBuffer->appendText(_fontHandle, _string);
+	bc.textBuffer->appendText(_fontHandle, _string, _end);
 }
 
-void TextBufferManager::appendText(TextBufferHandle _handle, FontHandle _fontHandle, const wchar_t* _string)
+void TextBufferManager::appendText(TextBufferHandle _handle, FontHandle _fontHandle, const wchar_t* _string, const wchar_t* _end)
 {
 	BX_CHECK(bgfx::invalidHandle != _handle.idx, "Invalid handle used");
 	BufferCache& bc = m_textBuffers[_handle.idx];
-	bc.textBuffer->appendText(_fontHandle, _string);
+	bc.textBuffer->appendText(_fontHandle, _string, _end);
 }
 
 void TextBufferManager::appendAtlasFace(TextBufferHandle _handle, uint16_t _faceIndex)

+ 7 - 6
examples/common/font/text_buffer_manager.h

@@ -55,19 +55,20 @@ public:
 	void setPenPosition(TextBufferHandle _handle, float _x, float _y);
 
 	/// append an ASCII/utf-8 string to the buffer using current pen position and color
-	void appendText(TextBufferHandle _handle, FontHandle _fontHandle, const char* _string);
+	void appendText(TextBufferHandle _handle, FontHandle _fontHandle, const char* _string, const char* _end = NULL);
 
 	/// append a wide char unicode string to the buffer using current pen position and color
-	void appendText(TextBufferHandle _handle, FontHandle _fontHandle, const wchar_t* _string);
-	
+	void appendText(TextBufferHandle _handle, FontHandle _fontHandle, const wchar_t* _string, const wchar_t* _end = NULL);
+		
 	/// append a whole face of the atlas cube, mostly used for debugging and visualizing atlas
 	void appendAtlasFace(TextBufferHandle _handle, uint16_t _faceIndex);
 
 	/// Clear the text buffer and reset its state (pen/color)
 	void clearTextBuffer(TextBufferHandle _handle);
-
-	TextRectangle getRectangle(TextBufferHandle _handle) const;
-
+	
+	/// Return the rectangular size of the current text buffer (including all its content)
+	TextRectangle getRectangle(TextBufferHandle _handle) const;	
+	
 private:
 	struct BufferCache
 	{

+ 303 - 0
examples/common/font/text_metrics.cpp

@@ -0,0 +1,303 @@
+/*
+ * Copyright 2013 Jeremie Roy. All rights reserved.
+ * License: http://www.opensource.org/licenses/BSD-2-Clause
+ */
+#include "text_metrics.h"
+#include <wchar.h>  // wcslen
+#include "utf8.h"
+
+TextMetrics::TextMetrics(FontManager* _fontManager): m_fontManager(_fontManager), m_width(0), m_height(0), m_x(0), m_lineHeight(0), m_lineGap(0)
+{
+}
+
+void TextMetrics::appendText(FontHandle _fontHandle, const char* _string)
+{
+	GlyphInfo glyph;
+	const FontInfo& font = m_fontManager->getFontInfo(_fontHandle);
+	
+	if(font.lineGap > m_lineGap) 
+	{
+		m_lineGap = font.lineGap;
+	}
+
+	if( (font.ascender - font.descender) > m_lineHeight)
+	{
+		m_height -= m_lineHeight;
+		m_lineHeight = font.ascender - font.descender;
+		m_height += m_lineHeight;
+	}
+
+	CodePoint codepoint = 0;
+	uint32_t state = 0;
+	
+	for (; *_string; ++_string)
+	{
+		if (!utf8_decode(&state, (uint32_t*)&codepoint, *_string) )
+		{
+			if (m_fontManager->getGlyphInfo(_fontHandle, codepoint, glyph) )
+			{
+				if (codepoint == L'\n')
+				{
+					m_height += m_lineGap + font.ascender - font.descender;					
+					m_lineGap = font.lineGap;
+					m_lineHeight = font.ascender - font.descender;					
+					m_x = 0;
+					break;
+				}
+				//TODO handle kerning
+				m_x += glyph.advance_x;
+				if(m_x > m_width)
+				{
+					m_width = m_x;
+				}
+			}
+			else
+			{
+				BX_CHECK(false, "Glyph not found");
+			}
+		}
+	}
+
+	BX_CHECK(state == UTF8_ACCEPT, "The string is not well-formed");
+}
+
+void TextMetrics::appendText(FontHandle _fontHandle, const wchar_t* _string)
+{
+	GlyphInfo glyph;
+	const FontInfo& font = m_fontManager->getFontInfo(_fontHandle);
+	
+	if(font.lineGap > m_lineGap) 
+	{
+		m_lineGap = font.lineGap;
+	}
+
+	if( (font.ascender - font.descender) > m_lineHeight)
+	{
+		m_height -= m_lineHeight;
+		m_lineHeight = font.ascender - font.descender;
+		m_height += m_lineHeight;
+	}
+
+	for (uint32_t ii = 0, end = wcslen(_string); ii < end; ++ii)
+	{
+		uint32_t codepoint = _string[ii];
+		if (m_fontManager->getGlyphInfo(_fontHandle, codepoint, glyph) )
+		{
+			if (codepoint == L'\n')
+			{
+				m_height += m_lineGap + font.ascender - font.descender;
+				m_lineGap = font.lineGap;
+				m_lineHeight = font.ascender - font.descender;
+				m_x = 0;
+				break;
+			}
+			//TODO handle kerning
+			m_x += glyph.advance_x;
+			if(m_x > m_width)
+			{
+				m_width = m_x;
+			}
+		}
+		else
+		{
+			BX_CHECK(false, "Glyph not found");
+		}
+	}
+}
+
+TextLineMetrics::TextLineMetrics(FontManager* _fontManager, FontHandle _fontHandle )
+{
+	const FontInfo& font = _fontManager->getFontInfo(_fontHandle);
+	m_lineHeight = font.ascender - font.descender + font.lineGap;
+}
+
+uint32_t TextLineMetrics::getLineCount(const char* _string) const
+{
+	CodePoint codepoint = 0;
+	uint32_t state = 0;
+	uint32_t lineCount = 1;
+	for (; *_string; ++_string)
+	{
+		if(utf8_decode(&state, (uint32_t*)&codepoint, *_string) == UTF8_ACCEPT)
+		{
+			if(codepoint == L'\n')
+			{				
+				++lineCount;
+			}
+		}
+	}
+	BX_CHECK(state == UTF8_ACCEPT, "The string is not well-formed");
+	return lineCount;
+}
+
+uint32_t TextLineMetrics::getLineCount(const wchar_t* _string) const
+{	
+	uint32_t lineCount = 1;
+	for ( ;*_string != L'\0'; ++_string)
+	{
+		if(*_string == L'\n')
+		{
+			++lineCount;
+		}
+	}
+	return lineCount;
+}
+
+
+void TextLineMetrics::getSubText(const char* _string, uint32_t _firstLine, uint32_t _lastLine, const char*& _begin, const char*& _end)
+{
+	CodePoint codepoint = 0;
+	uint32_t state = 0;
+	// y is bottom of a text line
+	uint32_t currentLine = 0;
+	while(*_string && (currentLine < _firstLine))
+	{
+		for (; *_string; ++_string)
+		{	
+			if(utf8_decode(&state, (uint32_t*)&codepoint, *_string) == UTF8_ACCEPT)
+			{
+				if(codepoint == L'\n')
+				{
+					++currentLine;
+					++_string;
+					break;
+				}
+			}
+		}
+	}
+	BX_CHECK(state == UTF8_ACCEPT, "The string is not well-formed");
+	_begin = _string;
+	
+	while((*_string) && (currentLine < _lastLine) )
+	{
+		for (; *_string; ++_string)
+		{	
+			if(utf8_decode(&state, (uint32_t*)&codepoint, *_string) == UTF8_ACCEPT)
+			{
+				if(codepoint == L'\n')
+				{
+					++currentLine;
+					++_string;
+					break;
+				}
+			}
+		}
+	}
+	BX_CHECK(state == UTF8_ACCEPT, "The string is not well-formed");
+	_end = _string;
+}
+
+void TextLineMetrics::getSubText(const wchar_t* _string, uint32_t _firstLine, uint32_t _lastLine, const wchar_t*& _begin, const wchar_t*& _end)
+{
+	uint32_t currentLine = 0;	
+	while((*_string != L'\0') && (currentLine < _firstLine))
+	{
+		for ( ;*_string != L'\0'; ++_string)
+		{			
+			if(*_string == L'\n')
+			{
+				++currentLine;
+				++_string;
+				break;
+			}
+		}
+	}
+	_begin = _string;
+
+	while((*_string != L'\0') && (currentLine < _lastLine) )
+	{
+		for ( ;*_string != L'\0'; ++_string)
+		{			
+			if(*_string == L'\n')
+			{
+				++currentLine;
+				++_string;
+				break;
+			}
+		}
+	}
+	_end = _string;	
+}
+
+void TextLineMetrics::getVisibleText(const char* _string, float _top, float _bottom, const char*& _begin, const char*& _end)
+{
+	CodePoint codepoint = 0;
+	uint32_t state = 0;
+	// y is bottom of a text line
+	float y = m_lineHeight;
+	while(*_string && (y < _top))
+	{
+		for (; *_string; ++_string)
+		{	
+			if(utf8_decode(&state, (uint32_t*)&codepoint, *_string) == UTF8_ACCEPT)
+			{
+				if(codepoint == L'\n')
+				{
+					y += m_lineHeight;
+					++_string;
+					break;
+				}
+			}
+		}
+	}
+	BX_CHECK(state == UTF8_ACCEPT, "The string is not well-formed");
+	_begin = _string;
+
+	// y is now top of a text line
+	y -= m_lineHeight;
+	while((*_string) && (y < _bottom) )
+	{
+		for (; *_string; ++_string)
+		{	
+			if(utf8_decode(&state, (uint32_t*)&codepoint, *_string) == UTF8_ACCEPT)
+			{
+				if(codepoint == L'\n')
+				{
+					y += m_lineHeight;
+					++_string;
+					break;
+				}
+			}
+		}
+	}
+	BX_CHECK(state == UTF8_ACCEPT, "The string is not well-formed");
+	_end = _string;
+}
+
+void TextLineMetrics::getVisibleText(const wchar_t* _string, float _top, float _bottom, const wchar_t*& _begin, const wchar_t*& _end)
+{
+	// y is bottom of a text line
+	float y = m_lineHeight;
+	
+	const wchar_t* _textEnd = _string + wcslen(_string);
+
+	while(y < _top)
+	{
+		for (const wchar_t* _current = _string; _current < _textEnd; ++_current)
+		{			
+			if(*_current == L'\n')
+			{
+				y += m_lineHeight;
+				++_string;
+				break;
+			}
+		}
+	}
+	_begin = _string;
+
+	// y is now top of a text line
+	y -= m_lineHeight;
+	while(y < _bottom )
+	{
+		for (const wchar_t* _current = _string; _current < _textEnd; ++_current)
+		{			
+			if(*_current == L'\n')
+			{
+				y += m_lineHeight;
+				++_string;
+				break;
+			}
+		}
+	}
+	_end = _string;	
+}

+ 67 - 0
examples/common/font/text_metrics.h

@@ -0,0 +1,67 @@
+/*
+ * Copyright 2013 Jeremie Roy. All rights reserved.
+ * License: http://www.opensource.org/licenses/BSD-2-Clause
+ */
+
+#ifndef __TEXT_METRICS_H__
+#define __TEXT_METRICS_H__
+
+#include "font_manager.h"
+
+class TextMetrics
+{
+public:
+	TextMetrics(FontManager* _fontManager);
+	
+	/// Append an ASCII/utf-8 string to the metrics helper
+	void appendText(FontHandle _fontHandle, const char* _string);
+
+	/// Append a wide char string to the metrics helper
+	void appendText(FontHandle _fontHandle, const wchar_t* _string);
+		
+	/// return the width of the measured text
+	float getWidth() const { return m_width; }
+
+	/// return the height of the measured text
+	float getHeight() const { return m_height; }
+
+private:
+	FontManager* m_fontManager;
+	float m_width;
+	float m_height;
+	float m_x;
+	float m_lineHeight;
+	float m_lineGap;
+};
+
+/// Compute text crop area for text using a single font
+class TextLineMetrics
+{
+public:
+	TextLineMetrics(FontManager* _fontManager, FontHandle _fontHandle);
+	
+	/// Return the height of a line of text using the given font
+	float getLineHeight() const { return m_lineHeight; }
+
+	/// Return the number of text line in the given text
+	uint32_t getLineCount(const char* _string) const;
+	/// Return the number of text line in the given text
+	uint32_t getLineCount(const wchar_t* _string) const;
+	
+	/// Return the first and last character visible in the [_firstLine, _lastLine[ range
+	void getSubText(const char* _string, uint32_t _firstLine, uint32_t _lastLine, const char*& _begin, const char*& _end);
+	/// Return the first and last character visible in the [_firstLine, _lastLine[ range
+	void getSubText(const wchar_t* _string, uint32_t _firstLine, uint32_t _lastLine, const wchar_t*& _begin, const wchar_t*& _end);
+
+	/// Return the first and last character visible in the [_top, _bottom] range,
+	void getVisibleText(const char* _string, float _top, float _bottom, const char*& _begin, const char*& _end);
+	/// Return the first and last character visible in the [_top, _bottom] range,
+	void getVisibleText(const wchar_t* _string, float _top, float _bottom, const wchar_t*& _begin, const wchar_t*& _end);
+
+private:
+	FontManager* m_fontManager;
+	float m_lineHeight;
+};
+
+
+#endif // __TEXT_METRICS_H__

BIN
examples/runtime/font/special_elite.ttf


+ 1136 - 0
examples/runtime/text/sherlock_holmes_a_scandal_in_bohemia_arthur_conan_doyle.txt

@@ -0,0 +1,1136 @@
+Stripped down from project Gutenberg's 
+"The Adventures of Sherlock Holmes", by Arthur Conan Doyle
+
+This eBook is for the use of anyone anywhere at no cost and with
+almost no restrictions whatsoever.  You may copy it, give it away or
+re-use it under the terms of the Project Gutenberg License included
+with this eBook or online at www.gutenberg.net
+
+
+ADVENTURE I. A SCANDAL IN BOHEMIA
+
+I.
+
+To Sherlock Holmes she is always THE woman. I have seldom heard
+him mention her under any other name. In his eyes she eclipses
+and predominates the whole of her sex. It was not that he felt
+any emotion akin to love for Irene Adler. All emotions, and that
+one particularly, were abhorrent to his cold, precise but
+admirably balanced mind. He was, I take it, the most perfect
+reasoning and observing machine that the world has seen, but as a
+lover he would have placed himself in a false position. He never
+spoke of the softer passions, save with a gibe and a sneer. They
+were admirable things for the observer--excellent for drawing the
+veil from men's motives and actions. But for the trained reasoner
+to admit such intrusions into his own delicate and finely
+adjusted temperament was to introduce a distracting factor which
+might throw a doubt upon all his mental results. Grit in a
+sensitive instrument, or a crack in one of his own high-power
+lenses, would not be more disturbing than a strong emotion in a
+nature such as his. And yet there was but one woman to him, and
+that woman was the late Irene Adler, of dubious and questionable
+memory.
+
+I had seen little of Holmes lately. My marriage had drifted us
+away from each other. My own complete happiness, and the
+home-centred interests which rise up around the man who first
+finds himself master of his own establishment, were sufficient to
+absorb all my attention, while Holmes, who loathed every form of
+society with his whole Bohemian soul, remained in our lodgings in
+Baker Street, buried among his old books, and alternating from
+week to week between cocaine and ambition, the drowsiness of the
+drug, and the fierce energy of his own keen nature. He was still,
+as ever, deeply attracted by the study of crime, and occupied his
+immense faculties and extraordinary powers of observation in
+following out those clues, and clearing up those mysteries which
+had been abandoned as hopeless by the official police. From time
+to time I heard some vague account of his doings: of his summons
+to Odessa in the case of the Trepoff murder, of his clearing up
+of the singular tragedy of the Atkinson brothers at Trincomalee,
+and finally of the mission which he had accomplished so
+delicately and successfully for the reigning family of Holland.
+Beyond these signs of his activity, however, which I merely
+shared with all the readers of the daily press, I knew little of
+my former friend and companion.
+
+One night--it was on the twentieth of March, 1888--I was
+returning from a journey to a patient (for I had now returned to
+civil practice), when my way led me through Baker Street. As I
+passed the well-remembered door, which must always be associated
+in my mind with my wooing, and with the dark incidents of the
+Study in Scarlet, I was seized with a keen desire to see Holmes
+again, and to know how he was employing his extraordinary powers.
+His rooms were brilliantly lit, and, even as I looked up, I saw
+his tall, spare figure pass twice in a dark silhouette against
+the blind. He was pacing the room swiftly, eagerly, with his head
+sunk upon his chest and his hands clasped behind him. To me, who
+knew his every mood and habit, his attitude and manner told their
+own story. He was at work again. He had risen out of his
+drug-created dreams and was hot upon the scent of some new
+problem. I rang the bell and was shown up to the chamber which
+had formerly been in part my own.
+
+His manner was not effusive. It seldom was; but he was glad, I
+think, to see me. With hardly a word spoken, but with a kindly
+eye, he waved me to an armchair, threw across his case of cigars,
+and indicated a spirit case and a gasogene in the corner. Then he
+stood before the fire and looked me over in his singular
+introspective fashion.
+
+"Wedlock suits you," he remarked. "I think, Watson, that you have
+put on seven and a half pounds since I saw you."
+
+"Seven!" I answered.
+
+"Indeed, I should have thought a little more. Just a trifle more,
+I fancy, Watson. And in practice again, I observe. You did not
+tell me that you intended to go into harness."
+
+"Then, how do you know?"
+
+"I see it, I deduce it. How do I know that you have been getting
+yourself very wet lately, and that you have a most clumsy and
+careless servant girl?"
+
+"My dear Holmes," said I, "this is too much. You would certainly
+have been burned, had you lived a few centuries ago. It is true
+that I had a country walk on Thursday and came home in a dreadful
+mess, but as I have changed my clothes I can't imagine how you
+deduce it. As to Mary Jane, she is incorrigible, and my wife has
+given her notice, but there, again, I fail to see how you work it
+out."
+
+He chuckled to himself and rubbed his long, nervous hands
+together.
+
+"It is simplicity itself," said he; "my eyes tell me that on the
+inside of your left shoe, just where the firelight strikes it,
+the leather is scored by six almost parallel cuts. Obviously they
+have been caused by someone who has very carelessly scraped round
+the edges of the sole in order to remove crusted mud from it.
+Hence, you see, my double deduction that you had been out in vile
+weather, and that you had a particularly malignant boot-slitting
+specimen of the London slavey. As to your practice, if a
+gentleman walks into my rooms smelling of iodoform, with a black
+mark of nitrate of silver upon his right forefinger, and a bulge
+on the right side of his top-hat to show where he has secreted
+his stethoscope, I must be dull, indeed, if I do not pronounce
+him to be an active member of the medical profession."
+
+I could not help laughing at the ease with which he explained his
+process of deduction. "When I hear you give your reasons," I
+remarked, "the thing always appears to me to be so ridiculously
+simple that I could easily do it myself, though at each
+successive instance of your reasoning I am baffled until you
+explain your process. And yet I believe that my eyes are as good
+as yours."
+
+"Quite so," he answered, lighting a cigarette, and throwing
+himself down into an armchair. "You see, but you do not observe.
+The distinction is clear. For example, you have frequently seen
+the steps which lead up from the hall to this room."
+
+"Frequently."
+
+"How often?"
+
+"Well, some hundreds of times."
+
+"Then how many are there?"
+
+"How many? I don't know."
+
+"Quite so! You have not observed. And yet you have seen. That is
+just my point. Now, I know that there are seventeen steps,
+because I have both seen and observed. By-the-way, since you are
+interested in these little problems, and since you are good
+enough to chronicle one or two of my trifling experiences, you
+may be interested in this." He threw over a sheet of thick,
+pink-tinted note-paper which had been lying open upon the table.
+"It came by the last post," said he. "Read it aloud."
+
+The note was undated, and without either signature or address.
+
+"There will call upon you to-night, at a quarter to eight
+o'clock," it said, "a gentleman who desires to consult you upon a
+matter of the very deepest moment. Your recent services to one of
+the royal houses of Europe have shown that you are one who may
+safely be trusted with matters which are of an importance which
+can hardly be exaggerated. This account of you we have from all
+quarters received. Be in your chamber then at that hour, and do
+not take it amiss if your visitor wear a mask."
+
+"This is indeed a mystery," I remarked. "What do you imagine that
+it means?"
+
+"I have no data yet. It is a capital mistake to theorize before
+one has data. Insensibly one begins to twist facts to suit
+theories, instead of theories to suit facts. But the note itself.
+What do you deduce from it?"
+
+I carefully examined the writing, and the paper upon which it was
+written.
+
+"The man who wrote it was presumably well to do," I remarked,
+endeavouring to imitate my companion's processes. "Such paper
+could not be bought under half a crown a packet. It is peculiarly
+strong and stiff."
+
+"Peculiar--that is the very word," said Holmes. "It is not an
+English paper at all. Hold it up to the light."
+
+I did so, and saw a large "E" with a small "g," a "P," and a
+large "G" with a small "t" woven into the texture of the paper.
+
+"What do you make of that?" asked Holmes.
+
+"The name of the maker, no doubt; or his monogram, rather."
+
+"Not at all. The 'G' with the small 't' stands for
+'Gesellschaft,' which is the German for 'Company.' It is a
+customary contraction like our 'Co.' 'P,' of course, stands for
+'Papier.' Now for the 'Eg.' Let us glance at our Continental
+Gazetteer." He took down a heavy brown volume from his shelves.
+"Eglow, Eglonitz--here we are, Egria. It is in a German-speaking
+country--in Bohemia, not far from Carlsbad. 'Remarkable as being
+the scene of the death of Wallenstein, and for its numerous
+glass-factories and paper-mills.' Ha, ha, my boy, what do you
+make of that?" His eyes sparkled, and he sent up a great blue
+triumphant cloud from his cigarette.
+
+"The paper was made in Bohemia," I said.
+
+"Precisely. And the man who wrote the note is a German. Do you
+note the peculiar construction of the sentence--'This account of
+you we have from all quarters received.' A Frenchman or Russian
+could not have written that. It is the German who is so
+uncourteous to his verbs. It only remains, therefore, to discover
+what is wanted by this German who writes upon Bohemian paper and
+prefers wearing a mask to showing his face. And here he comes, if
+I am not mistaken, to resolve all our doubts."
+
+As he spoke there was the sharp sound of horses' hoofs and
+grating wheels against the curb, followed by a sharp pull at the
+bell. Holmes whistled.
+
+"A pair, by the sound," said he. "Yes," he continued, glancing
+out of the window. "A nice little brougham and a pair of
+beauties. A hundred and fifty guineas apiece. There's money in
+this case, Watson, if there is nothing else."
+
+"I think that I had better go, Holmes."
+
+"Not a bit, Doctor. Stay where you are. I am lost without my
+Boswell. And this promises to be interesting. It would be a pity
+to miss it."
+
+"But your client--"
+
+"Never mind him. I may want your help, and so may he. Here he
+comes. Sit down in that armchair, Doctor, and give us your best
+attention."
+
+A slow and heavy step, which had been heard upon the stairs and
+in the passage, paused immediately outside the door. Then there
+was a loud and authoritative tap.
+
+"Come in!" said Holmes.
+
+A man entered who could hardly have been less than six feet six
+inches in height, with the chest and limbs of a Hercules. His
+dress was rich with a richness which would, in England, be looked
+upon as akin to bad taste. Heavy bands of astrakhan were slashed
+across the sleeves and fronts of his double-breasted coat, while
+the deep blue cloak which was thrown over his shoulders was lined
+with flame-coloured silk and secured at the neck with a brooch
+which consisted of a single flaming beryl. Boots which extended
+halfway up his calves, and which were trimmed at the tops with
+rich brown fur, completed the impression of barbaric opulence
+which was suggested by his whole appearance. He carried a
+broad-brimmed hat in his hand, while he wore across the upper
+part of his face, extending down past the cheekbones, a black
+vizard mask, which he had apparently adjusted that very moment,
+for his hand was still raised to it as he entered. From the lower
+part of the face he appeared to be a man of strong character,
+with a thick, hanging lip, and a long, straight chin suggestive
+of resolution pushed to the length of obstinacy.
+
+"You had my note?" he asked with a deep harsh voice and a
+strongly marked German accent. "I told you that I would call." He
+looked from one to the other of us, as if uncertain which to
+address.
+
+"Pray take a seat," said Holmes. "This is my friend and
+colleague, Dr. Watson, who is occasionally good enough to help me
+in my cases. Whom have I the honour to address?"
+
+"You may address me as the Count Von Kramm, a Bohemian nobleman.
+I understand that this gentleman, your friend, is a man of honour
+and discretion, whom I may trust with a matter of the most
+extreme importance. If not, I should much prefer to communicate
+with you alone."
+
+I rose to go, but Holmes caught me by the wrist and pushed me
+back into my chair. "It is both, or none," said he. "You may say
+before this gentleman anything which you may say to me."
+
+The Count shrugged his broad shoulders. "Then I must begin," said
+he, "by binding you both to absolute secrecy for two years; at
+the end of that time the matter will be of no importance. At
+present it is not too much to say that it is of such weight it
+may have an influence upon European history."
+
+"I promise," said Holmes.
+
+"And I."
+
+"You will excuse this mask," continued our strange visitor. "The
+august person who employs me wishes his agent to be unknown to
+you, and I may confess at once that the title by which I have
+just called myself is not exactly my own."
+
+"I was aware of it," said Holmes dryly.
+
+"The circumstances are of great delicacy, and every precaution
+has to be taken to quench what might grow to be an immense
+scandal and seriously compromise one of the reigning families of
+Europe. To speak plainly, the matter implicates the great House
+of Ormstein, hereditary kings of Bohemia."
+
+"I was also aware of that," murmured Holmes, settling himself
+down in his armchair and closing his eyes.
+
+Our visitor glanced with some apparent surprise at the languid,
+lounging figure of the man who had been no doubt depicted to him
+as the most incisive reasoner and most energetic agent in Europe.
+Holmes slowly reopened his eyes and looked impatiently at his
+gigantic client.
+
+"If your Majesty would condescend to state your case," he
+remarked, "I should be better able to advise you."
+
+The man sprang from his chair and paced up and down the room in
+uncontrollable agitation. Then, with a gesture of desperation, he
+tore the mask from his face and hurled it upon the ground. "You
+are right," he cried; "I am the King. Why should I attempt to
+conceal it?"
+
+"Why, indeed?" murmured Holmes. "Your Majesty had not spoken
+before I was aware that I was addressing Wilhelm Gottsreich
+Sigismond von Ormstein, Grand Duke of Cassel-Felstein, and
+hereditary King of Bohemia."
+
+"But you can understand," said our strange visitor, sitting down
+once more and passing his hand over his high white forehead, "you
+can understand that I am not accustomed to doing such business in
+my own person. Yet the matter was so delicate that I could not
+confide it to an agent without putting myself in his power. I
+have come incognito from Prague for the purpose of consulting
+you."
+
+"Then, pray consult," said Holmes, shutting his eyes once more.
+
+"The facts are briefly these: Some five years ago, during a
+lengthy visit to Warsaw, I made the acquaintance of the well-known
+adventuress, Irene Adler. The name is no doubt familiar to you."
+
+"Kindly look her up in my index, Doctor," murmured Holmes without
+opening his eyes. For many years he had adopted a system of
+docketing all paragraphs concerning men and things, so that it
+was difficult to name a subject or a person on which he could not
+at once furnish information. In this case I found her biography
+sandwiched in between that of a Hebrew rabbi and that of a
+staff-commander who had written a monograph upon the deep-sea
+fishes.
+
+"Let me see!" said Holmes. "Hum! Born in New Jersey in the year
+1858. Contralto--hum! La Scala, hum! Prima donna Imperial Opera
+of Warsaw--yes! Retired from operatic stage--ha! Living in
+London--quite so! Your Majesty, as I understand, became entangled
+with this young person, wrote her some compromising letters, and
+is now desirous of getting those letters back."
+
+"Precisely so. But how--"
+
+"Was there a secret marriage?"
+
+"None."
+
+"No legal papers or certificates?"
+
+"None."
+
+"Then I fail to follow your Majesty. If this young person should
+produce her letters for blackmailing or other purposes, how is
+she to prove their authenticity?"
+
+"There is the writing."
+
+"Pooh, pooh! Forgery."
+
+"My private note-paper."
+
+"Stolen."
+
+"My own seal."
+
+"Imitated."
+
+"My photograph."
+
+"Bought."
+
+"We were both in the photograph."
+
+"Oh, dear! That is very bad! Your Majesty has indeed committed an
+indiscretion."
+
+"I was mad--insane."
+
+"You have compromised yourself seriously."
+
+"I was only Crown Prince then. I was young. I am but thirty now."
+
+"It must be recovered."
+
+"We have tried and failed."
+
+"Your Majesty must pay. It must be bought."
+
+"She will not sell."
+
+"Stolen, then."
+
+"Five attempts have been made. Twice burglars in my pay ransacked
+her house. Once we diverted her luggage when she travelled. Twice
+she has been waylaid. There has been no result."
+
+"No sign of it?"
+
+"Absolutely none."
+
+Holmes laughed. "It is quite a pretty little problem," said he.
+
+"But a very serious one to me," returned the King reproachfully.
+
+"Very, indeed. And what does she propose to do with the
+photograph?"
+
+"To ruin me."
+
+"But how?"
+
+"I am about to be married."
+
+"So I have heard."
+
+"To Clotilde Lothman von Saxe-Meningen, second daughter of the
+King of Scandinavia. You may know the strict principles of her
+family. She is herself the very soul of delicacy. A shadow of a
+doubt as to my conduct would bring the matter to an end."
+
+"And Irene Adler?"
+
+"Threatens to send them the photograph. And she will do it. I
+know that she will do it. You do not know her, but she has a soul
+of steel. She has the face of the most beautiful of women, and
+the mind of the most resolute of men. Rather than I should marry
+another woman, there are no lengths to which she would not
+go--none."
+
+"You are sure that she has not sent it yet?"
+
+"I am sure."
+
+"And why?"
+
+"Because she has said that she would send it on the day when the
+betrothal was publicly proclaimed. That will be next Monday."
+
+"Oh, then we have three days yet," said Holmes with a yawn. "That
+is very fortunate, as I have one or two matters of importance to
+look into just at present. Your Majesty will, of course, stay in
+London for the present?"
+
+"Certainly. You will find me at the Langham under the name of the
+Count Von Kramm."
+
+"Then I shall drop you a line to let you know how we progress."
+
+"Pray do so. I shall be all anxiety."
+
+"Then, as to money?"
+
+"You have carte blanche."
+
+"Absolutely?"
+
+"I tell you that I would give one of the provinces of my kingdom
+to have that photograph."
+
+"And for present expenses?"
+
+The King took a heavy chamois leather bag from under his cloak
+and laid it on the table.
+
+"There are three hundred pounds in gold and seven hundred in
+notes," he said.
+
+Holmes scribbled a receipt upon a sheet of his note-book and
+handed it to him.
+
+"And Mademoiselle's address?" he asked.
+
+"Is Briony Lodge, Serpentine Avenue, St. John's Wood."
+
+Holmes took a note of it. "One other question," said he. "Was the
+photograph a cabinet?"
+
+"It was."
+
+"Then, good-night, your Majesty, and I trust that we shall soon
+have some good news for you. And good-night, Watson," he added,
+as the wheels of the royal brougham rolled down the street. "If
+you will be good enough to call to-morrow afternoon at three
+o'clock I should like to chat this little matter over with you."
+
+
+II.
+
+At three o'clock precisely I was at Baker Street, but Holmes had
+not yet returned. The landlady informed me that he had left the
+house shortly after eight o'clock in the morning. I sat down
+beside the fire, however, with the intention of awaiting him,
+however long he might be. I was already deeply interested in his
+inquiry, for, though it was surrounded by none of the grim and
+strange features which were associated with the two crimes which
+I have already recorded, still, the nature of the case and the
+exalted station of his client gave it a character of its own.
+Indeed, apart from the nature of the investigation which my
+friend had on hand, there was something in his masterly grasp of
+a situation, and his keen, incisive reasoning, which made it a
+pleasure to me to study his system of work, and to follow the
+quick, subtle methods by which he disentangled the most
+inextricable mysteries. So accustomed was I to his invariable
+success that the very possibility of his failing had ceased to
+enter into my head.
+
+It was close upon four before the door opened, and a
+drunken-looking groom, ill-kempt and side-whiskered, with an
+inflamed face and disreputable clothes, walked into the room.
+Accustomed as I was to my friend's amazing powers in the use of
+disguises, I had to look three times before I was certain that it
+was indeed he. With a nod he vanished into the bedroom, whence he
+emerged in five minutes tweed-suited and respectable, as of old.
+Putting his hands into his pockets, he stretched out his legs in
+front of the fire and laughed heartily for some minutes.
+
+"Well, really!" he cried, and then he choked and laughed again
+until he was obliged to lie back, limp and helpless, in the
+chair.
+
+"What is it?"
+
+"It's quite too funny. I am sure you could never guess how I
+employed my morning, or what I ended by doing."
+
+"I can't imagine. I suppose that you have been watching the
+habits, and perhaps the house, of Miss Irene Adler."
+
+"Quite so; but the sequel was rather unusual. I will tell you,
+however. I left the house a little after eight o'clock this
+morning in the character of a groom out of work. There is a
+wonderful sympathy and freemasonry among horsey men. Be one of
+them, and you will know all that there is to know. I soon found
+Briony Lodge. It is a bijou villa, with a garden at the back, but
+built out in front right up to the road, two stories. Chubb lock
+to the door. Large sitting-room on the right side, well
+furnished, with long windows almost to the floor, and those
+preposterous English window fasteners which a child could open.
+Behind there was nothing remarkable, save that the passage window
+could be reached from the top of the coach-house. I walked round
+it and examined it closely from every point of view, but without
+noting anything else of interest.
+
+"I then lounged down the street and found, as I expected, that
+there was a mews in a lane which runs down by one wall of the
+garden. I lent the ostlers a hand in rubbing down their horses,
+and received in exchange twopence, a glass of half and half, two
+fills of shag tobacco, and as much information as I could desire
+about Miss Adler, to say nothing of half a dozen other people in
+the neighbourhood in whom I was not in the least interested, but
+whose biographies I was compelled to listen to."
+
+"And what of Irene Adler?" I asked.
+
+"Oh, she has turned all the men's heads down in that part. She is
+the daintiest thing under a bonnet on this planet. So say the
+Serpentine-mews, to a man. She lives quietly, sings at concerts,
+drives out at five every day, and returns at seven sharp for
+dinner. Seldom goes out at other times, except when she sings.
+Has only one male visitor, but a good deal of him. He is dark,
+handsome, and dashing, never calls less than once a day, and
+often twice. He is a Mr. Godfrey Norton, of the Inner Temple. See
+the advantages of a cabman as a confidant. They had driven him
+home a dozen times from Serpentine-mews, and knew all about him.
+When I had listened to all they had to tell, I began to walk up
+and down near Briony Lodge once more, and to think over my plan
+of campaign.
+
+"This Godfrey Norton was evidently an important factor in the
+matter. He was a lawyer. That sounded ominous. What was the
+relation between them, and what the object of his repeated
+visits? Was she his client, his friend, or his mistress? If the
+former, she had probably transferred the photograph to his
+keeping. If the latter, it was less likely. On the issue of this
+question depended whether I should continue my work at Briony
+Lodge, or turn my attention to the gentleman's chambers in the
+Temple. It was a delicate point, and it widened the field of my
+inquiry. I fear that I bore you with these details, but I have to
+let you see my little difficulties, if you are to understand the
+situation."
+
+"I am following you closely," I answered.
+
+"I was still balancing the matter in my mind when a hansom cab
+drove up to Briony Lodge, and a gentleman sprang out. He was a
+remarkably handsome man, dark, aquiline, and moustached--evidently
+the man of whom I had heard. He appeared to be in a
+great hurry, shouted to the cabman to wait, and brushed past the
+maid who opened the door with the air of a man who was thoroughly
+at home.
+
+"He was in the house about half an hour, and I could catch
+glimpses of him in the windows of the sitting-room, pacing up and
+down, talking excitedly, and waving his arms. Of her I could see
+nothing. Presently he emerged, looking even more flurried than
+before. As he stepped up to the cab, he pulled a gold watch from
+his pocket and looked at it earnestly, 'Drive like the devil,' he
+shouted, 'first to Gross & Hankey's in Regent Street, and then to
+the Church of St. Monica in the Edgeware Road. Half a guinea if
+you do it in twenty minutes!'
+
+"Away they went, and I was just wondering whether I should not do
+well to follow them when up the lane came a neat little landau,
+the coachman with his coat only half-buttoned, and his tie under
+his ear, while all the tags of his harness were sticking out of
+the buckles. It hadn't pulled up before she shot out of the hall
+door and into it. I only caught a glimpse of her at the moment,
+but she was a lovely woman, with a face that a man might die for.
+
+"'The Church of St. Monica, John,' she cried, 'and half a
+sovereign if you reach it in twenty minutes.'
+
+"This was quite too good to lose, Watson. I was just balancing
+whether I should run for it, or whether I should perch behind her
+landau when a cab came through the street. The driver looked
+twice at such a shabby fare, but I jumped in before he could
+object. 'The Church of St. Monica,' said I, 'and half a sovereign
+if you reach it in twenty minutes.' It was twenty-five minutes to
+twelve, and of course it was clear enough what was in the wind.
+
+"My cabby drove fast. I don't think I ever drove faster, but the
+others were there before us. The cab and the landau with their
+steaming horses were in front of the door when I arrived. I paid
+the man and hurried into the church. There was not a soul there
+save the two whom I had followed and a surpliced clergyman, who
+seemed to be expostulating with them. They were all three
+standing in a knot in front of the altar. I lounged up the side
+aisle like any other idler who has dropped into a church.
+Suddenly, to my surprise, the three at the altar faced round to
+me, and Godfrey Norton came running as hard as he could towards
+me.
+
+"'Thank God,' he cried. 'You'll do. Come! Come!'
+
+"'What then?' I asked.
+
+"'Come, man, come, only three minutes, or it won't be legal.'
+
+"I was half-dragged up to the altar, and before I knew where I was
+I found myself mumbling responses which were whispered in my ear,
+and vouching for things of which I knew nothing, and generally
+assisting in the secure tying up of Irene Adler, spinster, to
+Godfrey Norton, bachelor. It was all done in an instant, and
+there was the gentleman thanking me on the one side and the lady
+on the other, while the clergyman beamed on me in front. It was
+the most preposterous position in which I ever found myself in my
+life, and it was the thought of it that started me laughing just
+now. It seems that there had been some informality about their
+license, that the clergyman absolutely refused to marry them
+without a witness of some sort, and that my lucky appearance
+saved the bridegroom from having to sally out into the streets in
+search of a best man. The bride gave me a sovereign, and I mean
+to wear it on my watch-chain in memory of the occasion."
+
+"This is a very unexpected turn of affairs," said I; "and what
+then?"
+
+"Well, I found my plans very seriously menaced. It looked as if
+the pair might take an immediate departure, and so necessitate
+very prompt and energetic measures on my part. At the church
+door, however, they separated, he driving back to the Temple, and
+she to her own house. 'I shall drive out in the park at five as
+usual,' she said as she left him. I heard no more. They drove
+away in different directions, and I went off to make my own
+arrangements."
+
+"Which are?"
+
+"Some cold beef and a glass of beer," he answered, ringing the
+bell. "I have been too busy to think of food, and I am likely to
+be busier still this evening. By the way, Doctor, I shall want
+your co-operation."
+
+"I shall be delighted."
+
+"You don't mind breaking the law?"
+
+"Not in the least."
+
+"Nor running a chance of arrest?"
+
+"Not in a good cause."
+
+"Oh, the cause is excellent!"
+
+"Then I am your man."
+
+"I was sure that I might rely on you."
+
+"But what is it you wish?"
+
+"When Mrs. Turner has brought in the tray I will make it clear to
+you. Now," he said as he turned hungrily on the simple fare that
+our landlady had provided, "I must discuss it while I eat, for I
+have not much time. It is nearly five now. In two hours we must
+be on the scene of action. Miss Irene, or Madame, rather, returns
+from her drive at seven. We must be at Briony Lodge to meet her."
+
+"And what then?"
+
+"You must leave that to me. I have already arranged what is to
+occur. There is only one point on which I must insist. You must
+not interfere, come what may. You understand?"
+
+"I am to be neutral?"
+
+"To do nothing whatever. There will probably be some small
+unpleasantness. Do not join in it. It will end in my being
+conveyed into the house. Four or five minutes afterwards the
+sitting-room window will open. You are to station yourself close
+to that open window."
+
+"Yes."
+
+"You are to watch me, for I will be visible to you."
+
+"Yes."
+
+"And when I raise my hand--so--you will throw into the room what
+I give you to throw, and will, at the same time, raise the cry of
+fire. You quite follow me?"
+
+"Entirely."
+
+"It is nothing very formidable," he said, taking a long cigar-shaped
+roll from his pocket. "It is an ordinary plumber's smoke-rocket,
+fitted with a cap at either end to make it self-lighting.
+Your task is confined to that. When you raise your cry of fire,
+it will be taken up by quite a number of people. You may then
+walk to the end of the street, and I will rejoin you in ten
+minutes. I hope that I have made myself clear?"
+
+"I am to remain neutral, to get near the window, to watch you,
+and at the signal to throw in this object, then to raise the cry
+of fire, and to wait you at the corner of the street."
+
+"Precisely."
+
+"Then you may entirely rely on me."
+
+"That is excellent. I think, perhaps, it is almost time that I
+prepare for the new role I have to play."
+
+He disappeared into his bedroom and returned in a few minutes in
+the character of an amiable and simple-minded Nonconformist
+clergyman. His broad black hat, his baggy trousers, his white
+tie, his sympathetic smile, and general look of peering and
+benevolent curiosity were such as Mr. John Hare alone could have
+equalled. It was not merely that Holmes changed his costume. His
+expression, his manner, his very soul seemed to vary with every
+fresh part that he assumed. The stage lost a fine actor, even as
+science lost an acute reasoner, when he became a specialist in
+crime.
+
+It was a quarter past six when we left Baker Street, and it still
+wanted ten minutes to the hour when we found ourselves in
+Serpentine Avenue. It was already dusk, and the lamps were just
+being lighted as we paced up and down in front of Briony Lodge,
+waiting for the coming of its occupant. The house was just such
+as I had pictured it from Sherlock Holmes' succinct description,
+but the locality appeared to be less private than I expected. On
+the contrary, for a small street in a quiet neighbourhood, it was
+remarkably animated. There was a group of shabbily dressed men
+smoking and laughing in a corner, a scissors-grinder with his
+wheel, two guardsmen who were flirting with a nurse-girl, and
+several well-dressed young men who were lounging up and down with
+cigars in their mouths.
+
+"You see," remarked Holmes, as we paced to and fro in front of
+the house, "this marriage rather simplifies matters. The
+photograph becomes a double-edged weapon now. The chances are
+that she would be as averse to its being seen by Mr. Godfrey
+Norton, as our client is to its coming to the eyes of his
+princess. Now the question is, Where are we to find the
+photograph?"
+
+"Where, indeed?"
+
+"It is most unlikely that she carries it about with her. It is
+cabinet size. Too large for easy concealment about a woman's
+dress. She knows that the King is capable of having her waylaid
+and searched. Two attempts of the sort have already been made. We
+may take it, then, that she does not carry it about with her."
+
+"Where, then?"
+
+"Her banker or her lawyer. There is that double possibility. But
+I am inclined to think neither. Women are naturally secretive,
+and they like to do their own secreting. Why should she hand it
+over to anyone else? She could trust her own guardianship, but
+she could not tell what indirect or political influence might be
+brought to bear upon a business man. Besides, remember that she
+had resolved to use it within a few days. It must be where she
+can lay her hands upon it. It must be in her own house."
+
+"But it has twice been burgled."
+
+"Pshaw! They did not know how to look."
+
+"But how will you look?"
+
+"I will not look."
+
+"What then?"
+
+"I will get her to show me."
+
+"But she will refuse."
+
+"She will not be able to. But I hear the rumble of wheels. It is
+her carriage. Now carry out my orders to the letter."
+
+As he spoke the gleam of the side-lights of a carriage came round
+the curve of the avenue. It was a smart little landau which
+rattled up to the door of Briony Lodge. As it pulled up, one of
+the loafing men at the corner dashed forward to open the door in
+the hope of earning a copper, but was elbowed away by another
+loafer, who had rushed up with the same intention. A fierce
+quarrel broke out, which was increased by the two guardsmen, who
+took sides with one of the loungers, and by the scissors-grinder,
+who was equally hot upon the other side. A blow was struck, and
+in an instant the lady, who had stepped from her carriage, was
+the centre of a little knot of flushed and struggling men, who
+struck savagely at each other with their fists and sticks. Holmes
+dashed into the crowd to protect the lady; but just as he reached
+her he gave a cry and dropped to the ground, with the blood
+running freely down his face. At his fall the guardsmen took to
+their heels in one direction and the loungers in the other, while
+a number of better-dressed people, who had watched the scuffle
+without taking part in it, crowded in to help the lady and to
+attend to the injured man. Irene Adler, as I will still call her,
+had hurried up the steps; but she stood at the top with her
+superb figure outlined against the lights of the hall, looking
+back into the street.
+
+"Is the poor gentleman much hurt?" she asked.
+
+"He is dead," cried several voices.
+
+"No, no, there's life in him!" shouted another. "But he'll be
+gone before you can get him to hospital."
+
+"He's a brave fellow," said a woman. "They would have had the
+lady's purse and watch if it hadn't been for him. They were a
+gang, and a rough one, too. Ah, he's breathing now."
+
+"He can't lie in the street. May we bring him in, marm?"
+
+"Surely. Bring him into the sitting-room. There is a comfortable
+sofa. This way, please!"
+
+Slowly and solemnly he was borne into Briony Lodge and laid out
+in the principal room, while I still observed the proceedings
+from my post by the window. The lamps had been lit, but the
+blinds had not been drawn, so that I could see Holmes as he lay
+upon the couch. I do not know whether he was seized with
+compunction at that moment for the part he was playing, but I
+know that I never felt more heartily ashamed of myself in my life
+than when I saw the beautiful creature against whom I was
+conspiring, or the grace and kindliness with which she waited
+upon the injured man. And yet it would be the blackest treachery
+to Holmes to draw back now from the part which he had intrusted
+to me. I hardened my heart, and took the smoke-rocket from under
+my ulster. After all, I thought, we are not injuring her. We are
+but preventing her from injuring another.
+
+Holmes had sat up upon the couch, and I saw him motion like a man
+who is in need of air. A maid rushed across and threw open the
+window. At the same instant I saw him raise his hand and at the
+signal I tossed my rocket into the room with a cry of "Fire!" The
+word was no sooner out of my mouth than the whole crowd of
+spectators, well dressed and ill--gentlemen, ostlers, and
+servant-maids--joined in a general shriek of "Fire!" Thick clouds
+of smoke curled through the room and out at the open window. I
+caught a glimpse of rushing figures, and a moment later the voice
+of Holmes from within assuring them that it was a false alarm.
+Slipping through the shouting crowd I made my way to the corner
+of the street, and in ten minutes was rejoiced to find my
+friend's arm in mine, and to get away from the scene of uproar.
+He walked swiftly and in silence for some few minutes until we
+had turned down one of the quiet streets which lead towards the
+Edgeware Road.
+
+"You did it very nicely, Doctor," he remarked. "Nothing could
+have been better. It is all right."
+
+"You have the photograph?"
+
+"I know where it is."
+
+"And how did you find out?"
+
+"She showed me, as I told you she would."
+
+"I am still in the dark."
+
+"I do not wish to make a mystery," said he, laughing. "The matter
+was perfectly simple. You, of course, saw that everyone in the
+street was an accomplice. They were all engaged for the evening."
+
+"I guessed as much."
+
+"Then, when the row broke out, I had a little moist red paint in
+the palm of my hand. I rushed forward, fell down, clapped my hand
+to my face, and became a piteous spectacle. It is an old trick."
+
+"That also I could fathom."
+
+"Then they carried me in. She was bound to have me in. What else
+could she do? And into her sitting-room, which was the very room
+which I suspected. It lay between that and her bedroom, and I was
+determined to see which. They laid me on a couch, I motioned for
+air, they were compelled to open the window, and you had your
+chance."
+
+"How did that help you?"
+
+"It was all-important. When a woman thinks that her house is on
+fire, her instinct is at once to rush to the thing which she
+values most. It is a perfectly overpowering impulse, and I have
+more than once taken advantage of it. In the case of the
+Darlington substitution scandal it was of use to me, and also in
+the Arnsworth Castle business. A married woman grabs at her baby;
+an unmarried one reaches for her jewel-box. Now it was clear to
+me that our lady of to-day had nothing in the house more precious
+to her than what we are in quest of. She would rush to secure it.
+The alarm of fire was admirably done. The smoke and shouting were
+enough to shake nerves of steel. She responded beautifully. The
+photograph is in a recess behind a sliding panel just above the
+right bell-pull. She was there in an instant, and I caught a
+glimpse of it as she half-drew it out. When I cried out that it
+was a false alarm, she replaced it, glanced at the rocket, rushed
+from the room, and I have not seen her since. I rose, and, making
+my excuses, escaped from the house. I hesitated whether to
+attempt to secure the photograph at once; but the coachman had
+come in, and as he was watching me narrowly it seemed safer to
+wait. A little over-precipitance may ruin all."
+
+"And now?" I asked.
+
+"Our quest is practically finished. I shall call with the King
+to-morrow, and with you, if you care to come with us. We will be
+shown into the sitting-room to wait for the lady, but it is
+probable that when she comes she may find neither us nor the
+photograph. It might be a satisfaction to his Majesty to regain
+it with his own hands."
+
+"And when will you call?"
+
+"At eight in the morning. She will not be up, so that we shall
+have a clear field. Besides, we must be prompt, for this marriage
+may mean a complete change in her life and habits. I must wire to
+the King without delay."
+
+We had reached Baker Street and had stopped at the door. He was
+searching his pockets for the key when someone passing said:
+
+"Good-night, Mister Sherlock Holmes."
+
+There were several people on the pavement at the time, but the
+greeting appeared to come from a slim youth in an ulster who had
+hurried by.
+
+"I've heard that voice before," said Holmes, staring down the
+dimly lit street. "Now, I wonder who the deuce that could have
+been."
+
+
+III.
+
+I slept at Baker Street that night, and we were engaged upon our
+toast and coffee in the morning when the King of Bohemia rushed
+into the room.
+
+"You have really got it!" he cried, grasping Sherlock Holmes by
+either shoulder and looking eagerly into his face.
+
+"Not yet."
+
+"But you have hopes?"
+
+"I have hopes."
+
+"Then, come. I am all impatience to be gone."
+
+"We must have a cab."
+
+"No, my brougham is waiting."
+
+"Then that will simplify matters." We descended and started off
+once more for Briony Lodge.
+
+"Irene Adler is married," remarked Holmes.
+
+"Married! When?"
+
+"Yesterday."
+
+"But to whom?"
+
+"To an English lawyer named Norton."
+
+"But she could not love him."
+
+"I am in hopes that she does."
+
+"And why in hopes?"
+
+"Because it would spare your Majesty all fear of future
+annoyance. If the lady loves her husband, she does not love your
+Majesty. If she does not love your Majesty, there is no reason
+why she should interfere with your Majesty's plan."
+
+"It is true. And yet--Well! I wish she had been of my own
+station! What a queen she would have made!" He relapsed into a
+moody silence, which was not broken until we drew up in
+Serpentine Avenue.
+
+The door of Briony Lodge was open, and an elderly woman stood
+upon the steps. She watched us with a sardonic eye as we stepped
+from the brougham.
+
+"Mr. Sherlock Holmes, I believe?" said she.
+
+"I am Mr. Holmes," answered my companion, looking at her with a
+questioning and rather startled gaze.
+
+"Indeed! My mistress told me that you were likely to call. She
+left this morning with her husband by the 5:15 train from Charing
+Cross for the Continent."
+
+"What!" Sherlock Holmes staggered back, white with chagrin and
+surprise. "Do you mean that she has left England?"
+
+"Never to return."
+
+"And the papers?" asked the King hoarsely. "All is lost."
+
+"We shall see." He pushed past the servant and rushed into the
+drawing-room, followed by the King and myself. The furniture was
+scattered about in every direction, with dismantled shelves and
+open drawers, as if the lady had hurriedly ransacked them before
+her flight. Holmes rushed at the bell-pull, tore back a small
+sliding shutter, and, plunging in his hand, pulled out a
+photograph and a letter. The photograph was of Irene Adler
+herself in evening dress, the letter was superscribed to
+"Sherlock Holmes, Esq. To be left till called for." My friend
+tore it open and we all three read it together. It was dated at
+midnight of the preceding night and ran in this way:
+
+"MY DEAR MR. SHERLOCK HOLMES,--You really did it very well. You
+took me in completely. Until after the alarm of fire, I had not a
+suspicion. But then, when I found how I had betrayed myself, I
+began to think. I had been warned against you months ago. I had
+been told that if the King employed an agent it would certainly
+be you. And your address had been given me. Yet, with all this,
+you made me reveal what you wanted to know. Even after I became
+suspicious, I found it hard to think evil of such a dear, kind
+old clergyman. But, you know, I have been trained as an actress
+myself. Male costume is nothing new to me. I often take advantage
+of the freedom which it gives. I sent John, the coachman, to
+watch you, ran up stairs, got into my walking-clothes, as I call
+them, and came down just as you departed.
+
+"Well, I followed you to your door, and so made sure that I was
+really an object of interest to the celebrated Mr. Sherlock
+Holmes. Then I, rather imprudently, wished you good-night, and
+started for the Temple to see my husband.
+
+"We both thought the best resource was flight, when pursued by
+so formidable an antagonist; so you will find the nest empty when
+you call to-morrow. As to the photograph, your client may rest in
+peace. I love and am loved by a better man than he. The King may
+do what he will without hindrance from one whom he has cruelly
+wronged. I keep it only to safeguard myself, and to preserve a
+weapon which will always secure me from any steps which he might
+take in the future. I leave a photograph which he might care to
+possess; and I remain, dear Mr. Sherlock Holmes,
+
+                                      "Very truly yours,
+                                   "IRENE NORTON, née ADLER."
+
+"What a woman--oh, what a woman!" cried the King of Bohemia, when
+we had all three read this epistle. "Did I not tell you how quick
+and resolute she was? Would she not have made an admirable queen?
+Is it not a pity that she was not on my level?"
+
+"From what I have seen of the lady she seems indeed to be on a
+very different level to your Majesty," said Holmes coldly. "I am
+sorry that I have not been able to bring your Majesty's business
+to a more successful conclusion."
+
+"On the contrary, my dear sir," cried the King; "nothing could be
+more successful. I know that her word is inviolate. The
+photograph is now as safe as if it were in the fire."
+
+"I am glad to hear your Majesty say so."
+
+"I am immensely indebted to you. Pray tell me in what way I can
+reward you. This ring--" He slipped an emerald snake ring from
+his finger and held it out upon the palm of his hand.
+
+"Your Majesty has something which I should value even more
+highly," said Holmes.
+
+"You have but to name it."
+
+"This photograph!"
+
+The King stared at him in amazement.
+
+"Irene's photograph!" he cried. "Certainly, if you wish it."
+
+"I thank your Majesty. Then there is no more to be done in the
+matter. I have the honour to wish you a very good-morning." He
+bowed, and, turning away without observing the hand which the
+King had stretched out to him, he set off in my company for his
+chambers.
+
+And that was how a great scandal threatened to affect the kingdom
+of Bohemia, and how the best plans of Mr. Sherlock Holmes were
+beaten by a woman's wit. He used to make merry over the
+cleverness of women, but I have not heard him do it of late. And
+when he speaks of Irene Adler, or when he refers to her
+photograph, it is always under the honourable title of the woman.