SkPDF: encode all bitmap images as Type3
Change-Id: Ic99c536ea7da6b6a55dd92395eee26c969fee966 Reviewed-on: https://skia-review.googlesource.com/158343 Reviewed-by: Ben Wagner <bungeman@google.com> Commit-Queue: Hal Canary <halcanary@google.com>
This commit is contained in:
parent
1d5b598c4e
commit
8ef78eae93
@ -52,25 +52,6 @@ public:
|
||||
sk_sp<SkPDFStream> fInvertFunction;
|
||||
sk_sp<SkPDFDict> fNoSmaskGraphicState;
|
||||
sk_sp<SkPDFArray> fRangeObject;
|
||||
|
||||
SK_BEGIN_REQUIRE_DENSE
|
||||
struct BitmapGlyphKey {
|
||||
SkFontID fFontID; // uint32_t
|
||||
SkScalar fTextSize; // float32
|
||||
SkScalar fTextScaleX; // float32
|
||||
SkScalar fTextSkewX; // float32
|
||||
SkGlyphID fGlyphID; // uint16_t
|
||||
uint16_t fPadding;
|
||||
};
|
||||
SK_END_REQUIRE_DENSE
|
||||
struct BitmapGlyph {
|
||||
sk_sp<SkImage> fImage;
|
||||
SkIPoint fOffset;
|
||||
};
|
||||
SkTHashMap<BitmapGlyphKey, BitmapGlyph> fBitmapGlyphImages;
|
||||
};
|
||||
|
||||
inline bool operator==(const SkPDFCanon::BitmapGlyphKey& u, const SkPDFCanon::BitmapGlyphKey& v) {
|
||||
return memcmp(&u, &v, sizeof(SkPDFCanon::BitmapGlyphKey)) == 0;
|
||||
}
|
||||
#endif // SkPDFCanon_DEFINED
|
||||
|
@ -937,12 +937,10 @@ class GlyphPositioner {
|
||||
public:
|
||||
GlyphPositioner(SkDynamicMemoryWStream* content,
|
||||
SkScalar textSkewX,
|
||||
bool wideChars,
|
||||
SkPoint origin)
|
||||
: fContent(content)
|
||||
, fCurrentMatrixOrigin(origin)
|
||||
, fTextSkewX(textSkewX)
|
||||
, fWideChars(wideChars) {
|
||||
, fTextSkewX(textSkewX) {
|
||||
}
|
||||
~GlyphPositioner() { this->flush(); }
|
||||
void flush() {
|
||||
@ -951,6 +949,10 @@ public:
|
||||
fInText = false;
|
||||
}
|
||||
}
|
||||
void setWideChars(bool wide) {
|
||||
this->flush();
|
||||
fWideChars = wide;
|
||||
}
|
||||
void writeGlyph(SkPoint xy,
|
||||
SkScalar advanceWidth,
|
||||
uint16_t glyph) {
|
||||
@ -995,7 +997,7 @@ private:
|
||||
SkPoint fCurrentMatrixOrigin;
|
||||
SkScalar fXAdvance = 0.0f;
|
||||
SkScalar fTextSkewX;
|
||||
bool fWideChars;
|
||||
bool fWideChars = true;
|
||||
bool fInText = false;
|
||||
bool fInitialized = false;
|
||||
};
|
||||
@ -1012,12 +1014,6 @@ struct PositionedGlyph {
|
||||
};
|
||||
}
|
||||
|
||||
static bool has_outline_glyph(SkGlyphID gid, SkGlyphCache* cache) {
|
||||
const SkGlyph& glyph = cache->getGlyphIDMetrics(gid);
|
||||
const SkPath* path = cache->findPath(glyph);
|
||||
return (path && !path->isEmpty()) || (glyph.fWidth == 0 && glyph.fHeight == 0);
|
||||
}
|
||||
|
||||
static SkRect get_glyph_bounds_device_space(SkGlyphID gid, SkGlyphCache* cache,
|
||||
SkScalar xScale, SkScalar yScale,
|
||||
SkPoint xy, const SkMatrix& ctm) {
|
||||
@ -1036,88 +1032,6 @@ static bool contains(const SkRect& r, SkPoint p) {
|
||||
r.top() <= p.y() && p.y() <= r.bottom();
|
||||
}
|
||||
|
||||
static sk_sp<SkImage> image_from_mask(const SkMask& mask) {
|
||||
if (!mask.fImage) {
|
||||
return nullptr;
|
||||
}
|
||||
SkIRect bounds = mask.fBounds;
|
||||
SkBitmap bm;
|
||||
switch (mask.fFormat) {
|
||||
case SkMask::kBW_Format:
|
||||
bm.allocPixels(SkImageInfo::MakeA8(bounds.width(), bounds.height()));
|
||||
for (int y = 0; y < bm.height(); ++y) {
|
||||
for (int x8 = 0; x8 < bm.width(); x8 += 8) {
|
||||
uint8_t v = *mask.getAddr1(x8 + bounds.x(), y + bounds.y());
|
||||
int e = SkTMin(x8 + 8, bm.width());
|
||||
for (int x = x8; x < e; ++x) {
|
||||
*bm.getAddr8(x, y) = (v >> (x & 0x7)) & 0x1 ? 0xFF : 0x00;
|
||||
}
|
||||
}
|
||||
}
|
||||
bm.setImmutable();
|
||||
return SkImage::MakeFromBitmap(bm);
|
||||
case SkMask::kA8_Format:
|
||||
bm.installPixels(SkImageInfo::MakeA8(bounds.width(), bounds.height()),
|
||||
mask.fImage, mask.fRowBytes);
|
||||
return SkMakeImageFromRasterBitmap(bm, kAlways_SkCopyPixelsMode);
|
||||
case SkMask::kARGB32_Format:
|
||||
bm.installPixels(SkImageInfo::MakeN32Premul(bounds.width(), bounds.height()),
|
||||
mask.fImage, mask.fRowBytes);
|
||||
return SkMakeImageFromRasterBitmap(bm, kAlways_SkCopyPixelsMode);
|
||||
case SkMask::k3D_Format:
|
||||
SkASSERT(false);
|
||||
return nullptr;
|
||||
case SkMask::kLCD16_Format:
|
||||
SkASSERT(false);
|
||||
return nullptr;
|
||||
default:
|
||||
SkASSERT(false);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void draw_missing_glyphs(SkPDFDevice* dev, const SkPaint& paint, SkTypeface* typeface,
|
||||
const std::vector<PositionedGlyph>& missingGlyphs) {
|
||||
if (missingGlyphs.size() == 0) {
|
||||
return;
|
||||
}
|
||||
// Fall back on images.
|
||||
SkPaint scaledGlyphCachePaint;
|
||||
scaledGlyphCachePaint.setTextSize(paint.getTextSize());
|
||||
scaledGlyphCachePaint.setTextScaleX(paint.getTextScaleX());
|
||||
scaledGlyphCachePaint.setTextSkewX(paint.getTextSkewX());
|
||||
scaledGlyphCachePaint.setTypeface(sk_ref_sp(typeface));
|
||||
auto scaledGlyphCache = SkStrikeCache::FindOrCreateStrikeExclusive(scaledGlyphCachePaint);
|
||||
SkTHashMap<SkPDFCanon::BitmapGlyphKey, SkPDFCanon::BitmapGlyph>* map =
|
||||
&dev->getCanon()->fBitmapGlyphImages;
|
||||
for (PositionedGlyph positionedGlyph : missingGlyphs) {
|
||||
SkPDFCanon::BitmapGlyphKey key = {typeface->uniqueID(),
|
||||
paint.getTextSize(),
|
||||
paint.getTextScaleX(),
|
||||
paint.getTextSkewX(),
|
||||
positionedGlyph.fGlyph,
|
||||
0};
|
||||
SkImage* img = nullptr;
|
||||
SkIPoint imgOffset = {0, 0};
|
||||
if (SkPDFCanon::BitmapGlyph* ptr = map->find(key)) {
|
||||
img = ptr->fImage.get();
|
||||
imgOffset = ptr->fOffset;
|
||||
} else {
|
||||
(void)scaledGlyphCache->findImage(
|
||||
scaledGlyphCache->getGlyphIDMetrics(positionedGlyph.fGlyph));
|
||||
SkMask mask;
|
||||
scaledGlyphCache->getGlyphIDMetrics(positionedGlyph.fGlyph).toMask(&mask);
|
||||
imgOffset = {mask.fBounds.x(), mask.fBounds.y()};
|
||||
img = map->set(key, {image_from_mask(mask), imgOffset})->fImage.get();
|
||||
}
|
||||
if (img) {
|
||||
SkPoint pt = positionedGlyph.fPos +
|
||||
SkPoint{(SkScalar)imgOffset.x(), (SkScalar)imgOffset.y()};
|
||||
dev->drawImage(img, pt.x(), pt.y(), paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkPDFDevice::drawGlyphRunAsPath(const SkGlyphRun& glyphRun, SkPoint offset) {
|
||||
const SkPaint& paint = glyphRun.paint();
|
||||
SkPath path;
|
||||
@ -1150,21 +1064,6 @@ void SkPDFDevice::drawGlyphRunAsPath(const SkGlyphRun& glyphRun, SkPoint offset)
|
||||
} else {
|
||||
this->internalDrawGlyphRun(tmp, offset);
|
||||
}
|
||||
|
||||
if (!tmp.paint().getTypeface()) {
|
||||
return;
|
||||
}
|
||||
std::vector<PositionedGlyph> missingGlyphs;
|
||||
int emSize;
|
||||
auto glyphCache = SkPDFFont::MakeVectorCache(tmp.paint().getTypeface(), &emSize);
|
||||
for (size_t i = 0; i < glyphRun.shuntGlyphsIDs().size(); ++i) {
|
||||
SkGlyphID gid = glyphRun.shuntGlyphsIDs()[i];
|
||||
if (!has_outline_glyph(gid, glyphCache.get())) {
|
||||
SkPoint xy = glyphRun.positions()[i];
|
||||
missingGlyphs.push_back({xy + offset, gid});
|
||||
}
|
||||
}
|
||||
draw_missing_glyphs(this, paint, tmp.paint().getTypeface(), missingGlyphs);
|
||||
}
|
||||
|
||||
void SkPDFDevice::internalDrawGlyphRun(const SkGlyphRun& glyphRun, SkPoint offset) {
|
||||
@ -1217,7 +1116,6 @@ void SkPDFDevice::internalDrawGlyphRun(const SkGlyphRun& glyphRun, SkPoint offse
|
||||
|
||||
SkASSERT(paint.getTextAlign() == SkPaint::kLeft_Align);
|
||||
SkRect clipStackBounds = this->cs().bounds(this->bounds());
|
||||
std::vector<PositionedGlyph> missingGlyphs;
|
||||
{
|
||||
ScopedContentEntry content(this, paint, true);
|
||||
if (!content.entry()) {
|
||||
@ -1243,15 +1141,11 @@ void SkPDFDevice::internalDrawGlyphRun(const SkGlyphRun& glyphRun, SkPoint offse
|
||||
|
||||
const SkGlyphID maxGlyphID = SkToU16(typeface->countGlyphs() - 1);
|
||||
|
||||
bool multiByteGlyphs = SkPDFFont::IsMultiByte(SkPDFFont::FontType(*metrics));
|
||||
if (clusterator.reversedChars()) {
|
||||
out->writeText("/ReversedChars BMC\n");
|
||||
}
|
||||
SK_AT_SCOPE_EXIT(if (clusterator.reversedChars()) { out->writeText("EMC\n"); } );
|
||||
GlyphPositioner glyphPositioner(out,
|
||||
paint.getTextSkewX(),
|
||||
multiByteGlyphs,
|
||||
offset);
|
||||
GlyphPositioner glyphPositioner(out, paint.getTextSkewX(), offset);
|
||||
SkPDFFont* font = nullptr;
|
||||
|
||||
while (SkClusterator::Cluster c = clusterator.next()) {
|
||||
@ -1300,7 +1194,8 @@ void SkPDFDevice::internalDrawGlyphRun(const SkGlyphRun& glyphRun, SkPoint offse
|
||||
if (!font || !font->hasGlyph(gid)) {
|
||||
// Not yet specified font or need to switch font.
|
||||
sk_sp<SkPDFFont> newFont =
|
||||
SkPDFFont::GetFontResource(fDocument->canon(), typeface, gid);
|
||||
SkPDFFont::GetFontResource(
|
||||
fDocument->canon(), glyphCache.get(), typeface, gid);
|
||||
SkASSERT(newFont); // All preconditions for SkPDFFont::GetFontResource are met.
|
||||
if (!newFont) {
|
||||
return;
|
||||
@ -1310,13 +1205,12 @@ void SkPDFDevice::internalDrawGlyphRun(const SkGlyphRun& glyphRun, SkPoint offse
|
||||
int fontIndex = find_or_add(&fFontResources, std::move(newFont));
|
||||
|
||||
glyphPositioner.flush();
|
||||
|
||||
glyphPositioner.setWideChars(font->multiByteGlyphs());
|
||||
SkPDFWriteResourceName(out, SkPDFResourceType::kFont, fontIndex);
|
||||
out->writeText(" ");
|
||||
SkPDFUtils::AppendScalar(textSize, out);
|
||||
out->writeText(" Tf\n");
|
||||
|
||||
SkASSERT(font->multiByteGlyphs() == multiByteGlyphs);
|
||||
}
|
||||
SkPoint xy = glyphRun.positions()[index];
|
||||
// Do a glyph-by-glyph bounds-reject if positions are absolute.
|
||||
@ -1332,21 +1226,15 @@ void SkPDFDevice::internalDrawGlyphRun(const SkGlyphRun& glyphRun, SkPoint offse
|
||||
continue; // reject glyphs as out of bounds
|
||||
}
|
||||
}
|
||||
if (!has_outline_glyph(gid, glyphCache.get())) {
|
||||
missingGlyphs.push_back({xy + offset, gid});
|
||||
}
|
||||
|
||||
font->noteGlyphUsage(gid);
|
||||
|
||||
SkGlyphID encodedGlyph = multiByteGlyphs ? gid : font->glyphToPDFFontEncoding(gid);
|
||||
SkGlyphID encodedGlyph = font->multiByteGlyphs()
|
||||
? gid : font->glyphToPDFFontEncoding(gid);
|
||||
SkScalar advance = advanceScale * glyphCache->getGlyphIDAdvance(gid).fAdvanceX;
|
||||
glyphPositioner.writeGlyph(xy, advance, encodedGlyph);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (paint.getColor() != SK_ColorTRANSPARENT) {
|
||||
draw_missing_glyphs(this, srcPaint, typeface, missingGlyphs);
|
||||
}
|
||||
}
|
||||
|
||||
void SkPDFDevice::drawGlyphRunList(const SkGlyphRunList& glyphRunList) {
|
||||
|
@ -9,13 +9,16 @@
|
||||
|
||||
#include "SkData.h"
|
||||
#include "SkGlyphCache.h"
|
||||
#include "SkImagePriv.h"
|
||||
#include "SkMacros.h"
|
||||
#include "SkMakeUnique.h"
|
||||
#include "SkPDFBitmap.h"
|
||||
#include "SkPDFCanon.h"
|
||||
#include "SkPDFConvertType1FontStream.h"
|
||||
#include "SkPDFDevice.h"
|
||||
#include "SkPDFMakeCIDGlyphWidthsArray.h"
|
||||
#include "SkPDFMakeToUnicodeCmap.h"
|
||||
#include "SkPDFResourceDict.h"
|
||||
#include "SkPDFUtils.h"
|
||||
#include "SkPaint.h"
|
||||
#include "SkRefCnt.h"
|
||||
@ -205,7 +208,14 @@ static SkGlyphID first_nonzero_glyph_for_single_byte_encoding(SkGlyphID gid) {
|
||||
return gid != 0 ? gid - (gid - 1) % 255 : 1;
|
||||
}
|
||||
|
||||
static bool has_outline_glyph(SkGlyphID gid, SkGlyphCache* cache) {
|
||||
const SkGlyph& glyph = cache->getGlyphIDMetrics(gid);
|
||||
const SkPath* path = cache->findPath(glyph);
|
||||
return (path && !path->isEmpty()) || (glyph.fWidth == 0 && glyph.fHeight == 0);
|
||||
}
|
||||
|
||||
sk_sp<SkPDFFont> SkPDFFont::GetFontResource(SkPDFCanon* canon,
|
||||
SkGlyphCache* cache,
|
||||
SkTypeface* face,
|
||||
SkGlyphID glyphID) {
|
||||
SkASSERT(canon);
|
||||
@ -215,6 +225,9 @@ sk_sp<SkPDFFont> SkPDFFont::GetFontResource(SkPDFCanon* canon,
|
||||
// GetMetrics only returns null to signify a bad typeface.
|
||||
const SkAdvancedTypefaceMetrics& metrics = *fontMetrics;
|
||||
SkAdvancedTypefaceMetrics::FontType type = SkPDFFont::FontType(metrics);
|
||||
if (!has_outline_glyph(glyphID, cache)) {
|
||||
type = SkAdvancedTypefaceMetrics::kOther_Font;
|
||||
}
|
||||
bool multibyte = SkPDFFont::IsMultiByte(type);
|
||||
SkGlyphID subsetCode = multibyte ? 0 : first_nonzero_glyph_for_single_byte_encoding(glyphID);
|
||||
uint64_t fontID = (static_cast<uint64_t>(SkTypeface::UniqueID(face)) << 16) | subsetCode;
|
||||
@ -636,6 +649,51 @@ private:
|
||||
};
|
||||
}
|
||||
|
||||
struct ImageAndOffset {
|
||||
sk_sp<SkImage> fImage;
|
||||
SkIPoint fOffset;
|
||||
};
|
||||
static ImageAndOffset to_image(SkGlyphID gid, SkGlyphCache* cache) {
|
||||
(void)cache->findImage(cache->getGlyphIDMetrics(gid));
|
||||
SkMask mask;
|
||||
cache->getGlyphIDMetrics(gid).toMask(&mask);
|
||||
if (!mask.fImage) {
|
||||
return {nullptr, {0, 0}};
|
||||
}
|
||||
SkIRect bounds = mask.fBounds;
|
||||
SkBitmap bm;
|
||||
switch (mask.fFormat) {
|
||||
case SkMask::kBW_Format:
|
||||
bm.allocPixels(SkImageInfo::MakeA8(bounds.width(), bounds.height()));
|
||||
for (int y = 0; y < bm.height(); ++y) {
|
||||
for (int x8 = 0; x8 < bm.width(); x8 += 8) {
|
||||
uint8_t v = *mask.getAddr1(x8 + bounds.x(), y + bounds.y());
|
||||
int e = SkTMin(x8 + 8, bm.width());
|
||||
for (int x = x8; x < e; ++x) {
|
||||
*bm.getAddr8(x, y) = (v >> (x & 0x7)) & 0x1 ? 0xFF : 0x00;
|
||||
}
|
||||
}
|
||||
}
|
||||
bm.setImmutable();
|
||||
return {SkImage::MakeFromBitmap(bm), {bounds.x(), bounds.y()}};
|
||||
case SkMask::kA8_Format:
|
||||
bm.installPixels(SkImageInfo::MakeA8(bounds.width(), bounds.height()),
|
||||
mask.fImage, mask.fRowBytes);
|
||||
return {SkMakeImageFromRasterBitmap(bm, kAlways_SkCopyPixelsMode),
|
||||
{bounds.x(), bounds.y()}};
|
||||
case SkMask::kARGB32_Format:
|
||||
bm.installPixels(SkImageInfo::MakeN32Premul(bounds.width(), bounds.height()),
|
||||
mask.fImage, mask.fRowBytes);
|
||||
return {SkMakeImageFromRasterBitmap(bm, kAlways_SkCopyPixelsMode),
|
||||
{bounds.x(), bounds.y()}};
|
||||
case SkMask::k3D_Format:
|
||||
case SkMask::kLCD16_Format:
|
||||
default:
|
||||
SkASSERT(false);
|
||||
return {nullptr, {0, 0}};
|
||||
}
|
||||
}
|
||||
|
||||
static void add_type3_font_info(SkPDFCanon* canon,
|
||||
SkPDFDict* font,
|
||||
SkTypeface* typeface,
|
||||
@ -704,12 +762,35 @@ static void add_type3_font_info(SkPDFCanon* canon,
|
||||
characterName, sk_make_sp<SkPDFStream>(
|
||||
std::unique_ptr<SkStreamAsset>(content.detachAsStream())));
|
||||
} else {
|
||||
if (!emptyStream) {
|
||||
emptyStream = sk_make_sp<SkPDFStream>(
|
||||
std::unique_ptr<SkStreamAsset>(
|
||||
new SkMemoryStream((size_t)0)));
|
||||
auto pimg = to_image(gID, cache.get());
|
||||
if (!pimg.fImage) {
|
||||
if (!emptyStream) {
|
||||
emptyStream = sk_make_sp<SkPDFStream>(
|
||||
std::unique_ptr<SkStreamAsset>(
|
||||
new SkMemoryStream((size_t)0)));
|
||||
}
|
||||
charProcs->insertObjRef(characterName, emptyStream);
|
||||
} else {
|
||||
SkDynamicMemoryWStream content;
|
||||
SkPDFUtils::AppendScalar(SkFloatToScalar(glyph.fAdvanceX), &content);
|
||||
content.writeText(" 0 d0\n");
|
||||
content.writeDecAsText(pimg.fImage->width());
|
||||
content.writeText(" 0 0 ");
|
||||
content.writeDecAsText(-pimg.fImage->height());
|
||||
content.writeText(" ");
|
||||
content.writeDecAsText(pimg.fOffset.x());
|
||||
content.writeText(" ");
|
||||
content.writeDecAsText(pimg.fImage->height() + pimg.fOffset.y());
|
||||
content.writeText(" cm\n");
|
||||
content.writeText("/X Do\n");
|
||||
auto proc = sk_make_sp<SkPDFStream>(content.detachAsStream());
|
||||
auto d0 = sk_make_sp<SkPDFDict>();
|
||||
d0->insertObjRef("X", SkPDFCreateBitmapObject(std::move(pimg.fImage)));
|
||||
auto d1 = sk_make_sp<SkPDFDict>();
|
||||
d1->insertObject("XObject", std::move(d0));
|
||||
proc->dict()->insertObject("Resources", std::move(d1));
|
||||
charProcs->insertObjRef(characterName, std::move(proc));
|
||||
}
|
||||
charProcs->insertObjRef(characterName, emptyStream);
|
||||
}
|
||||
}
|
||||
encDiffs->appendName(characterName.c_str());
|
||||
|
@ -11,13 +11,11 @@
|
||||
|
||||
#include "SkAdvancedTypefaceMetrics.h"
|
||||
#include "SkBitSet.h"
|
||||
#include "SkStrikeCache.h"
|
||||
#include "SkPDFCanon.h"
|
||||
#include "SkPDFTypes.h"
|
||||
#include "SkStrikeCache.h"
|
||||
#include "SkTypeface.h"
|
||||
|
||||
class SkPDFCanon;
|
||||
class SkPDFFont;
|
||||
|
||||
/** \class SkPDFFont
|
||||
A PDF Object class representing a font. The font may have resources
|
||||
attached to it in order to embed the font. SkPDFFonts are canonicalized
|
||||
@ -84,6 +82,7 @@ public:
|
||||
* @param glyphID Specify which section of a large font is of interest.
|
||||
*/
|
||||
static sk_sp<SkPDFFont> GetFontResource(SkPDFCanon* canon,
|
||||
SkGlyphCache* cache,
|
||||
SkTypeface* typeface,
|
||||
SkGlyphID glyphID);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user