bool HtmlFormatter::EmitImage(ImageData *img) { CrashIf(!img->data); Size imgSize = BitmapSizeFromData(img->data, img->len); if (imgSize.Empty()) return false; SizeF newSize((REAL)imgSize.Width, (REAL)imgSize.Height); // move overly large images to a new line (if they don't fit entirely) if (!IsCurrLineEmpty() && (currX + newSize.Width > pageDx || currY + newSize.Height > pageDy)) FlushCurrLine(false); // move overly large images to a new page // (if they don't fit even when scaled down to 75%) REAL scalePage = min((pageDx - currX) / newSize.Width, pageDy / newSize.Height); if (currY > 0 && currY + newSize.Height * min(scalePage, 0.75f) > pageDy) ForceNewPage(); // if image is bigger than the available space, scale it down if (newSize.Width > pageDx - currX || newSize.Height > pageDy - currY) { REAL scale = min(scalePage, (pageDy - currY) / newSize.Height); // scale down images that follow right after a line // containing a single image as little as possible, // as they might be intended to be of the same size if (scale < scalePage && HasPreviousLineSingleImage(currPage->instructions)) { ForceNewPage(); scale = scalePage; } if (scale < 1) { newSize.Width = min(newSize.Width * scale, pageDx - currX); newSize.Height = min(newSize.Height * scale, pageDy - currY); } } RectF bbox(PointF(currX, 0), newSize); AppendInstr(DrawInstr::Image(img->data, img->len, bbox)); currX += bbox.Width; return true; }
// adapted from http://cpansearch.perl.org/src/RJRAY/Image-Size-3.230/lib/Image/Size.pm Size BitmapSizeFromData(const char *data, size_t len) { Size result; ByteReader r(data, len); switch (GfxFormatFromData(data, len)) { case Img_BMP: if (len >= sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)) { BITMAPINFOHEADER bmi; bool ok = r.UnpackLE(&bmi, sizeof(bmi), "3d2w6d", sizeof(BITMAPFILEHEADER)); CrashIf(!ok); result.Width = bmi.biWidth; result.Height = bmi.biHeight; } break; case Img_GIF: if (len >= 13) { // find the first image's actual size instead of using the // "logical screen" size which is sometimes too large size_t ix = 13; // skip the global color table if ((r.Byte(10) & 0x80)) ix += 3 * (1 << ((r.Byte(10) & 0x07) + 1)); while (ix + 8 < len) { if (r.Byte(ix) == 0x2C) { result.Width = r.WordLE(ix + 5); result.Height = r.WordLE(ix + 7); break; } else if (r.Byte(ix) == 0x21 && r.Byte(ix + 1) == 0xF9) ix += 8; else if (r.Byte(ix) == 0x21 && r.Byte(ix + 1) == 0xFE) { const char *commentEnd = r.Find(ix + 2, 0x00); ix = commentEnd ? commentEnd - data + 1 : len; } else if (r.Byte(ix) == 0x21 && r.Byte(ix + 1) == 0x01 && ix + 15 < len) { const char *textDataEnd = r.Find(ix + 15, 0x00); ix = textDataEnd ? textDataEnd - data + 1 : len; } else if (r.Byte(ix) == 0x21 && r.Byte(ix + 1) == 0xFF && ix + 14 < len) { const char *applicationDataEnd = r.Find(ix + 14, 0x00); ix = applicationDataEnd ? applicationDataEnd - data + 1 : len; } else break; } } break; case Img_JPEG: // find the last start of frame marker for non-differential Huffman/arithmetic coding for (size_t ix = 2; ix + 9 < len && r.Byte(ix) == 0xFF; ) { if (0xC0 <= r.Byte(ix + 1) && r.Byte(ix + 1) <= 0xC3 || 0xC9 <= r.Byte(ix + 1) && r.Byte(ix + 1) <= 0xCB) { result.Width = r.WordBE(ix + 7); result.Height = r.WordBE(ix + 5); } ix += r.WordBE(ix + 2) + 2; } break; case Img_JXR: case Img_TIFF: if (len >= 10) { bool isBE = r.Byte(0) == 'M', isJXR = r.Byte(2) == 0xBC; CrashIf(!isBE && r.Byte(0) != 'I' || isJXR && isBE); const WORD WIDTH = isJXR ? 0xBC80 : 0x0100, HEIGHT = isJXR ? 0xBC81 : 0x0101; size_t idx = r.DWord(4, isBE); WORD count = idx <= len - 2 ? r.Word(idx, isBE) : 0; for (idx += 2; count > 0 && idx <= len - 12; count--, idx += 12) { WORD tag = r.Word(idx, isBE), type = r.Word(idx + 2, isBE); if (r.DWord(idx + 4, isBE) != 1) continue; else if (WIDTH == tag && 4 == type) result.Width = r.DWord(idx + 8, isBE); else if (WIDTH == tag && 3 == type) result.Width = r.Word(idx + 8, isBE); else if (WIDTH == tag && 1 == type) result.Width = r.Byte(idx + 8); else if (HEIGHT == tag && 4 == type) result.Height = r.DWord(idx + 8, isBE); else if (HEIGHT == tag && 3 == type) result.Height = r.Word(idx + 8, isBE); else if (HEIGHT == tag && 1 == type) result.Height = r.Byte(idx + 8); } } break; case Img_PNG: if (len >= 24 && str::StartsWith(data + 12, "IHDR")) { result.Width = r.DWordBE(16); result.Height = r.DWordBE(20); } break; case Img_TGA: if (len >= 16) { result.Width = r.WordLE(12); result.Height = r.WordLE(14); } break; case Img_WebP: if (len >= 30 && str::StartsWith(data + 12, "VP8 ")) { result.Width = r.WordLE(26) & 0x3fff; result.Height = r.WordLE(28) & 0x3fff; } else { result = webp::SizeFromData(data, len); } break; case Img_JP2: if (len >= 32) { size_t ix = 0; while (ix < len - 32) { uint32_t lbox = r.DWordBE(ix); uint32_t tbox = r.DWordBE(ix + 4); if (0x6A703268 /* jp2h */ == tbox) { ix += 8; if (r.DWordBE(ix) == 24 && r.DWordBE(ix + 4) == 0x69686472 /* ihdr */) { result.Width = r.DWordBE(ix + 16); result.Height = r.DWordBE(ix + 12); } break; } else if (lbox != 0 && ix < UINT32_MAX - lbox) { ix += lbox; } else { break; } } } break; } if (result.Empty()) { // let GDI+ extract the image size if we've failed // (currently happens for animated GIF) Bitmap *bmp = BitmapFromData(data, len); if (bmp) result = Size(bmp->GetWidth(), bmp->GetHeight()); delete bmp; } return result; }
// adapted from http://cpansearch.perl.org/src/RJRAY/Image-Size-3.230/lib/Image/Size.pm Size BitmapSizeFromData(const char *data, size_t len) { Size result; ByteReader r(data, len); switch (GfxFormatFromData(data, len)) { case Img_BMP: if (len >= sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)) { BITMAPINFOHEADER bmi; bool ok = r.UnpackLE(&bmi, sizeof(bmi), "3d2w6d", sizeof(BITMAPFILEHEADER)); CrashIf(!ok); result.Width = bmi.biWidth; result.Height = bmi.biHeight; } break; case Img_GIF: if (len >= 13) { // find the first image's actual size instead of using the // "logical screen" size which is sometimes too large size_t ix = 13; // skip the global color table if ((r.Byte(10) & 0x80)) ix += 3 * (1 << ((r.Byte(10) & 0x07) + 1)); while (ix + 8 < len) { if (r.Byte(ix) == 0x2C) { result.Width = r.WordLE(ix + 5); result.Height = r.WordLE(ix + 7); break; } else if (r.Byte(ix) == 0x21 && r.Byte(ix + 1) == 0xF9) ix += 8; else if (r.Byte(ix) == 0x21 && r.Byte(ix + 1) == 0xFE) { const char *commentEnd = r.Find(ix + 2, 0x00); ix = commentEnd ? commentEnd - data + 1 : len; } else if (r.Byte(ix) == 0x21 && r.Byte(ix + 1) == 0x01 && ix + 15 < len) { const char *textDataEnd = r.Find(ix + 15, 0x00); ix = textDataEnd ? textDataEnd - data + 1 : len; } else if (r.Byte(ix) == 0x21 && r.Byte(ix + 1) == 0xFF && ix + 14 < len) { const char *applicationDataEnd = r.Find(ix + 14, 0x00); ix = applicationDataEnd ? applicationDataEnd - data + 1 : len; } else break; } } break; case Img_JPEG: // find the last start of frame marker for non-differential Huffman coding for (size_t ix = 2; ix + 9 < len && r.Byte(ix) == 0xFF; ) { if (0xC0 <= r.Byte(ix + 1) && r.Byte(ix + 1) <= 0xC3) { result.Width = r.WordBE(ix + 7); result.Height = r.WordBE(ix + 5); } ix += r.WordBE(ix + 2) + 2; } break; case Img_JXR: if (len >= 10 && r.DWordLE(4) == 8) { WORD ifdLen = r.WordLE(8); for (size_t i = 0; i < ifdLen && 10 + (i + 1) * 12 < len; i++) { size_t idx = 10 + i * 12; if (r.WordLE(idx + 0) == 0xBC80 && r.WordLE(idx + 2) == 4 && r.DWordLE(idx + 4) == 1) result.Width = r.DWordLE(idx + 8); if (r.WordLE(idx + 0) == 0xBC80 && r.WordLE(idx + 2) == 3 && r.DWordLE(idx + 4) == 1) result.Width = r.WordLE(idx + 8); if (r.WordLE(idx + 0) == 0xBC80 && r.WordLE(idx + 2) == 1 && r.DWordLE(idx + 4) == 1) result.Width = r.Byte(idx + 8); if (r.WordLE(idx + 0) == 0xBC81 && r.WordLE(idx + 2) == 4 && r.DWordLE(idx + 4) == 1) result.Height = r.DWordLE(idx + 8); if (r.WordLE(idx + 0) == 0xBC81 && r.WordLE(idx + 2) == 3 && r.DWordLE(idx + 4) == 1) result.Height = r.WordLE(idx + 8); if (r.WordLE(idx + 0) == 0xBC81 && r.WordLE(idx + 2) == 1 && r.DWordLE(idx + 4) == 1) result.Height = r.Byte(idx + 8); } } break; case Img_PNG: if (len >= 24 && str::StartsWith(data + 12, "IHDR")) { result.Width = r.DWordBE(16); result.Height = r.DWordBE(20); } break; case Img_TGA: if (len >= 16) { result.Width = r.WordLE(12); result.Height = r.WordLE(14); } break; case Img_TIFF: // TODO: speed this up (if necessary) break; } if (result.Empty()) { // let GDI+ extract the image size if we've failed // (currently happens for animated GIFs and for all TIFFs) Bitmap *bmp = BitmapFromData(data, len); if (bmp) result = Size(bmp->GetWidth(), bmp->GetHeight()); delete bmp; } return result; }