mirror of
https://github.com/pdf2htmlEX/pdf2htmlEX.git
synced 2024-10-05 19:41:40 +00:00
9ed21007e5
* Show header in font map files * fix a usage of unique_ptr with array * Added '--quiet' argument to hide progress messages (resolves #503) * Revert cout messages to cerr (see #622) * bump version * fix build; fix some coverity warnings * Many bug fixes and improvements, including: - Incorporated latest Cairo files from cairo-0.15.2 - Moved build to out-of-source - Added clean script - Rewritten correct_text_visibility option to improve accuracy - Transparent characters drawn on background layer - Improved bad unicode detection * Many bug fixes and improvements, including: - Incorporated latest Cairo files from cairo-0.15.2 - Moved build to out-of-source - Added clean script - Rewritten correct_text_visibility option to improve accuracy - Transparent characters drawn on background layer - Improved bad unicode detection * Rationlise DPI to single number. Implement actual_dpi - clamp maximum background image size in cases of huge PDF pages * DPI fixes - increase DPI when partially covered text to covered-text-dpi Add font-style italic for oblique fonts Reduce char bbox for occlusion tests * Don't shrink bbox - not required if zoom=25 used * Ignore occlusion from stroke/fill with opacity < 0.5 Better compute char bbox for occlusion Use 10% inset for char bbox for occlusion Back out adding font-weight: bold to potentially bold fonts Fix bug to ensure CID ascent/descent matches subfont values * Removed zero char logging * Remove forced italic - missing italic is due to fontforge bug which needs fixing * Typos fixed, readme updated * Typos * Increase maximum background image width Fix private use range to avoid stupid mobile safari switching to emoji font * included -pthread switch to link included 3rdparty poppler files. * Updated files from poppler 0.59.0 and adjusted includes. * Support updated "Object" class from poppler 0.59.0
448 lines
14 KiB
C++
448 lines
14 KiB
C++
/*
|
|
* DrawingTracer.cc
|
|
*
|
|
* Created on: 2014-6-15
|
|
* Author: duanyao
|
|
*/
|
|
|
|
#include "GfxFont.h"
|
|
|
|
#include "util/math.h"
|
|
#include "DrawingTracer.h"
|
|
|
|
#if !ENABLE_SVG
|
|
#error "ENABLE_SVG must be enabled"
|
|
#endif
|
|
|
|
//#define DEBUG
|
|
|
|
namespace pdf2htmlEX
|
|
{
|
|
|
|
DrawingTracer::DrawingTracer(const Param & param): param(param), cairo(nullptr)
|
|
{
|
|
}
|
|
|
|
DrawingTracer::~DrawingTracer()
|
|
{
|
|
finish();
|
|
}
|
|
|
|
void DrawingTracer::reset(GfxState *state)
|
|
{
|
|
finish();
|
|
|
|
// pbox is defined in device space, which is affected by zooming;
|
|
// We want to trace in page space which is stable, so invert pbox by ctm.
|
|
double pbox[] { 0, 0, state->getPageWidth(), state->getPageHeight() };
|
|
Matrix ctm, ictm;
|
|
state->getCTM(&ctm);
|
|
ctm.invertTo(&ictm);
|
|
tm_transform_bbox(ictm.m, pbox);
|
|
cairo_rectangle_t page_box { pbox[0], pbox[1], pbox[2] - pbox[0], pbox[3] - pbox[1] };
|
|
cairo_surface_t * surface = cairo_recording_surface_create(CAIRO_CONTENT_COLOR_ALPHA, &page_box);
|
|
cairo = cairo_create(surface);
|
|
|
|
ctm_stack.clear();
|
|
double *identity = new double[6];
|
|
tm_init(identity);
|
|
ctm_stack.push_back(identity);
|
|
|
|
#ifdef DEBUG
|
|
printf("DrawingTracer::reset:page bbox:[%f,%f,%f,%f]\n",pbox[0], pbox[1], pbox[2], pbox[3]);
|
|
#endif
|
|
}
|
|
|
|
void DrawingTracer::finish()
|
|
{
|
|
if (cairo)
|
|
{
|
|
cairo_destroy(cairo);
|
|
cairo = nullptr;
|
|
}
|
|
}
|
|
|
|
// Poppler won't inform us its initial CTM, and the initial CTM is affected by zoom level.
|
|
// OutputDev::clip() may be called before OutputDev::updateCTM(), so we can't rely on GfxState::getCTM(),
|
|
// and should trace ctm changes ourself (via cairo).
|
|
void DrawingTracer::update_ctm(GfxState *state, double m11, double m12, double m21, double m22, double m31, double m32)
|
|
{
|
|
if (!param.correct_text_visibility)
|
|
return;
|
|
|
|
double *tmp = new double[6];
|
|
tmp[0] = m11;
|
|
tmp[1] = m12;
|
|
tmp[2] = m21;
|
|
tmp[3] = m22;
|
|
tmp[4] = m31;
|
|
tmp[5] = m32;
|
|
double *ctm = ctm_stack.back();
|
|
tm_multiply(ctm, tmp);
|
|
#ifdef DEBUG
|
|
printf("DrawingTracer::before update_ctm:ctm:[%f,%f,%f,%f,%f,%f] => [%f,%f,%f,%f,%f,%f]\n", m11, m12, m21, m22, m31, m32, ctm[0], ctm[1], ctm[2], ctm[3], ctm[4], ctm[5]);
|
|
#endif
|
|
}
|
|
|
|
void DrawingTracer::clip(GfxState * state, bool even_odd)
|
|
{
|
|
if (!param.correct_text_visibility)
|
|
return;
|
|
do_path(state, state->getPath());
|
|
cairo_set_fill_rule(cairo, even_odd? CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING);
|
|
cairo_clip (cairo);
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
double cbox[4];
|
|
cairo_clip_extents(cairo, cbox, cbox + 1, cbox + 2, cbox + 3);
|
|
printf("DrawingTracer::clip:extents:even_odd=%d,[%f,%f,%f,%f]\n", even_odd, cbox[0],cbox[1],cbox[2],cbox[3]);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void DrawingTracer::clip_to_stroke_path(GfxState * state)
|
|
{
|
|
if (!param.correct_text_visibility)
|
|
return;
|
|
|
|
printf("TODO:clip_to_stroke_path\n");
|
|
// TODO cairo_stroke_to_path() ?
|
|
}
|
|
|
|
void DrawingTracer::save()
|
|
{
|
|
if (!param.correct_text_visibility)
|
|
return;
|
|
cairo_save(cairo);
|
|
double *e = new double[6];
|
|
memcpy(e, ctm_stack.back(), sizeof(double) * 6);
|
|
ctm_stack.push_back(e);
|
|
|
|
#ifdef DEBUG
|
|
printf("DrawingTracer::saved: [%f,%f,%f,%f,%f,%f]\n", e[0], e[1], e[2], e[3], e[4], e[5]);
|
|
#endif
|
|
}
|
|
void DrawingTracer::restore()
|
|
{
|
|
if (!param.correct_text_visibility)
|
|
return;
|
|
cairo_restore(cairo);
|
|
ctm_stack.pop_back();
|
|
|
|
#ifdef DEBUG
|
|
double *ctm = ctm_stack.back();
|
|
printf("DrawingTracer::restored: [%f,%f,%f,%f,%f,%f]\n", ctm[0], ctm[1], ctm[2], ctm[3], ctm[4], ctm[5]);
|
|
#endif
|
|
}
|
|
|
|
void DrawingTracer::do_path(GfxState * state, GfxPath * path)
|
|
{
|
|
//copy from CairoOutputDev::doPath
|
|
GfxSubpath *subpath;
|
|
int i, j;
|
|
double x, y;
|
|
cairo_new_path(cairo);
|
|
#ifdef DEBUG
|
|
printf("DrawingTracer::do_path:new_path (%d subpaths)\n", path->getNumSubpaths());
|
|
#endif
|
|
|
|
for (i = 0; i < path->getNumSubpaths(); ++i) {
|
|
subpath = path->getSubpath(i);
|
|
if (subpath->getNumPoints() > 0) {
|
|
x = subpath->getX(0);
|
|
y = subpath->getY(0);
|
|
xform_pt(x, y);
|
|
cairo_move_to(cairo, x, y);
|
|
j = 1;
|
|
while (j < subpath->getNumPoints()) {
|
|
if (subpath->getCurve(j)) {
|
|
x = subpath->getX(j+2);
|
|
y = subpath->getY(j+2);
|
|
double x1 = subpath->getX(j);
|
|
double y1 = subpath->getY(j);
|
|
double x2 = subpath->getX(j+1);
|
|
double y2 = subpath->getY(j+1);
|
|
xform_pt(x, y);
|
|
xform_pt(x1, y1);
|
|
xform_pt(x2, y2);
|
|
cairo_curve_to(cairo,
|
|
x1, y1,
|
|
x2, y2,
|
|
x, y);
|
|
j += 3;
|
|
} else {
|
|
x = subpath->getX(j);
|
|
y = subpath->getY(j);
|
|
xform_pt(x, y);
|
|
cairo_line_to(cairo, x, y);
|
|
++j;
|
|
}
|
|
}
|
|
if (subpath->isClosed()) {
|
|
cairo_close_path (cairo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DrawingTracer::stroke(GfxState * state)
|
|
{
|
|
if (!param.correct_text_visibility)
|
|
return;
|
|
|
|
if (state->getStrokeOpacity() < 0.5) {
|
|
// Ignore partially transparent fills for occlusion purposes
|
|
return;
|
|
}
|
|
|
|
// Transform the line width by the ctm. This isn't 100% - we should really do this path segment by path segment,
|
|
// this is a reasonable approximation providing the CTM has uniform scaling X/Y
|
|
double lwx, lwy;
|
|
lwx = lwy = sqrt(0.5);
|
|
tm_transform(ctm_stack.back(), lwx, lwy, true);
|
|
double lineWidthScale = sqrt(lwx * lwx + lwy * lwy);
|
|
#ifdef DEBUG
|
|
printf("DrawingTracer::stroke. line width = %f*%f, line cap = %d\n", lineWidthScale, state->getLineWidth(), state->getLineCap());
|
|
#endif
|
|
cairo_set_line_width(cairo, lineWidthScale * state->getLineWidth());
|
|
|
|
// Line cap is important - some PDF line widths are very large
|
|
switch (state->getLineCap()) {
|
|
case 0:
|
|
cairo_set_line_cap (cairo, CAIRO_LINE_CAP_BUTT);
|
|
break;
|
|
case 1:
|
|
cairo_set_line_cap (cairo, CAIRO_LINE_CAP_ROUND);
|
|
break;
|
|
case 2:
|
|
cairo_set_line_cap (cairo, CAIRO_LINE_CAP_SQUARE);
|
|
break;
|
|
}
|
|
|
|
GfxPath * path = state->getPath();
|
|
for (int i = 0; i < path->getNumSubpaths(); ++i) {
|
|
GfxSubpath * subpath = path->getSubpath(i);
|
|
if (subpath->getNumPoints() <= 0)
|
|
continue;
|
|
double x = subpath->getX(0);
|
|
double y = subpath->getY(0);
|
|
xform_pt(x, y);
|
|
//p: loop cursor; j: next point index
|
|
int p =1;
|
|
int n = subpath->getNumPoints();
|
|
while (p < n) {
|
|
cairo_new_path(cairo);
|
|
#ifdef DEBUG
|
|
printf("move_to: [%f,%f]\n", x, y);
|
|
#endif
|
|
cairo_move_to(cairo, x, y);
|
|
if (subpath->getCurve(p)) {
|
|
x = subpath->getX(p+2);
|
|
y = subpath->getY(p+2);
|
|
double x1 = subpath->getX(p);
|
|
double y1 = subpath->getY(p);
|
|
double x2 = subpath->getX(p+1);
|
|
double y2 = subpath->getY(p+1);
|
|
xform_pt(x, y);
|
|
xform_pt(x1, y1);
|
|
xform_pt(x2, y2);
|
|
#ifdef DEBUG
|
|
printf("curve_to: [%f,%f], [%f,%f], [%f,%f]\n", x1, y1, x2, y2, x, y);
|
|
#endif
|
|
cairo_curve_to(cairo,
|
|
x1, y1,
|
|
x2, y2,
|
|
x, y);
|
|
p += 3;
|
|
} else {
|
|
x = subpath->getX(p);
|
|
y = subpath->getY(p);
|
|
xform_pt(x, y);
|
|
#ifdef DEBUG
|
|
printf("line_to: [%f,%f]\n", x, y);
|
|
#endif
|
|
cairo_line_to(cairo, x, y);
|
|
++p;
|
|
}
|
|
|
|
double sbox[4];
|
|
cairo_stroke_extents(cairo, sbox, sbox + 1, sbox + 2, sbox + 3);
|
|
#ifdef DEBUG
|
|
printf("DrawingTracer::stroke:new box:[%f,%f,%f,%f]\n", sbox[0], sbox[1], sbox[2], sbox[3]);
|
|
#endif
|
|
if (sbox[0] != sbox[2] && sbox[1] != sbox[3])
|
|
draw_non_char_bbox(state, sbox, 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DrawingTracer::fill(GfxState * state, bool even_odd)
|
|
{
|
|
if (!param.correct_text_visibility)
|
|
return;
|
|
|
|
if (state->getFillOpacity() < 0.5) {
|
|
// Ignore partially transparent fills for occlusion purposes
|
|
return;
|
|
}
|
|
|
|
do_path(state, state->getPath());
|
|
//cairo_fill_extents don't take fill rule into account.
|
|
//cairo_set_fill_rule (cairo, even_odd? CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING);
|
|
double fbox[4];
|
|
cairo_fill_extents(cairo, fbox, fbox + 1, fbox + 2, fbox + 3);
|
|
|
|
#ifdef DEBUG
|
|
printf("DrawingTracer::fill:[%f,%f,%f,%f]\n", fbox[0],fbox[1],fbox[2],fbox[3]);
|
|
#endif
|
|
|
|
draw_non_char_bbox(state, fbox, 1);
|
|
}
|
|
|
|
void DrawingTracer::draw_non_char_bbox(GfxState * state, double * bbox, int what)
|
|
{
|
|
// what == 0 => just do bbox text
|
|
// what == 1 => stroke test
|
|
// what == 2 => fill test
|
|
double cbox[4];
|
|
cairo_clip_extents(cairo, cbox, cbox + 1, cbox + 2, cbox + 3);
|
|
if(bbox_intersect(cbox, bbox))
|
|
{
|
|
#ifdef DEBUG
|
|
printf("DrawingTracer::draw_non_char_bbox:what=%d,[%f,%f,%f,%f]\n", what, bbox[0],bbox[1],bbox[2],bbox[3]);
|
|
#endif
|
|
if (on_non_char_drawn)
|
|
on_non_char_drawn(cairo, bbox, what);
|
|
}
|
|
}
|
|
|
|
void DrawingTracer::draw_char_bbox(GfxState * state, double * bbox, int inTransparencyGroup)
|
|
{
|
|
if (inTransparencyGroup || state->getFillOpacity() < 1.0 || state->getStrokeOpacity() < 1.0) {
|
|
on_char_clipped(cairo, bbox, 0);
|
|
return;
|
|
}
|
|
if (!param.correct_text_visibility) {
|
|
double bbox[4] = { 0, 0, 0, 0 }; // bbox not relevant if not correcting text visibility
|
|
on_char_drawn(cairo, bbox);
|
|
return;
|
|
}
|
|
|
|
double cbox[4];
|
|
cairo_clip_extents(cairo, cbox, cbox + 1, cbox + 2, cbox + 3);
|
|
#ifdef DEBUG
|
|
printf("DrawingTracer::draw_char_bbox::char bbox[%f,%f,%f,%f],clip extents:[%f,%f,%f,%f]\n", bbox[0], bbox[1], bbox[2], bbox[3], cbox[0],cbox[1],cbox[2],cbox[3]);
|
|
#endif
|
|
|
|
if (bbox_intersect(bbox, cbox)) {
|
|
#ifdef DEBUG
|
|
printf("char intersects clip\n");
|
|
#endif
|
|
int pts_visible = 0;
|
|
// See which points are inside the current clip
|
|
if (cairo_in_clip(cairo, bbox[0], bbox[1]))
|
|
pts_visible |= 1;
|
|
if (cairo_in_clip(cairo, bbox[2], bbox[1]))
|
|
pts_visible |= 2;
|
|
if (cairo_in_clip(cairo, bbox[2], bbox[3]))
|
|
pts_visible |= 4;
|
|
if (cairo_in_clip(cairo, bbox[0], bbox[3]))
|
|
pts_visible |= 8;
|
|
|
|
if (pts_visible == (1|2|4|8)) {
|
|
#ifdef DEBUG
|
|
printf("char inside clip\n");
|
|
#endif
|
|
on_char_drawn(cairo, bbox);
|
|
} else {
|
|
#ifdef DEBUG
|
|
printf("char partial clip (%x)\n", pts_visible);
|
|
#endif
|
|
on_char_clipped(cairo, bbox, pts_visible);
|
|
}
|
|
} else {
|
|
#ifdef DEBUG
|
|
printf("char outside clip\n");
|
|
#endif
|
|
on_char_clipped(cairo, bbox, 0);
|
|
}
|
|
}
|
|
|
|
void DrawingTracer::draw_image(GfxState *state)
|
|
{
|
|
if (!param.correct_text_visibility)
|
|
return;
|
|
|
|
double x1, y1, x2, y2, x3, y3, x4, y4;
|
|
x1 = x4 = y3 = y4 = 0;
|
|
x2 = y2 = x3 = y1 = 1;
|
|
|
|
xform_pt(x1, y1);
|
|
xform_pt(x2, y2);
|
|
xform_pt(x3, y3);
|
|
xform_pt(x4, y4);
|
|
|
|
cairo_new_path(cairo);
|
|
cairo_move_to(cairo, x1, y1);
|
|
cairo_line_to(cairo, x2, y2);
|
|
cairo_line_to(cairo, x3, y3);
|
|
cairo_line_to(cairo, x4, y4);
|
|
cairo_close_path (cairo);
|
|
#ifdef DEBUG
|
|
printf("draw_image: [%f,%f], [%f,%f], [%f,%f], [%f,%f]\n", x1, y1, x2, y2, x3, y3, x4, y4);
|
|
#endif
|
|
|
|
double bbox[4] {0, 0, 1, 1};
|
|
tm_transform_bbox(ctm_stack.back(), bbox);
|
|
|
|
draw_non_char_bbox(state, bbox, 1);
|
|
}
|
|
|
|
void DrawingTracer::draw_char(GfxState *state, double x, double y, double width, double height, int inTransparencyGroup)
|
|
{
|
|
//printf("x=%f,y=%f,width=%f,height=%f\n", x, y, width, height);
|
|
Matrix tm, itm;
|
|
memcpy(tm.m, state->getTextMat(), sizeof(tm.m));
|
|
|
|
//printf("tm = %f,%f,%f,%f,%f,%f\n", tm.m[0], tm.m[1], tm.m[2], tm.m[3], tm.m[4], tm.m[5]);
|
|
double cx = state->getCurX(), cy = state->getCurY(), fs = state->getFontSize(),
|
|
ry = state->getRise(), h = state->getHorizScaling();
|
|
|
|
//printf("cx=%f,cy=%f,fs=%f,ry=%f,h=%f\n", cx,cy,fs,ry,h);
|
|
|
|
//cx and cy has been transformed by text matrix, we need to reverse them.
|
|
tm.invertTo(&itm);
|
|
double char_cx, char_cy;
|
|
itm.transform(cx, cy, &char_cx, &char_cy);
|
|
|
|
//printf("char_cx = %f, char_cy = %f\n", char_cx, char_cy);
|
|
|
|
//TODO Vertical? Currently vertical/type3 chars are treated as non-chars.
|
|
double char_m[6] {fs * h, 0, 0, fs, char_cx + x, char_cy + y + ry};
|
|
|
|
//printf("char_m = %f,%f,%f,%f,%f,%f\n", char_m[0], char_m[1], char_m[2], char_m[3], char_m[4], char_m[5]);
|
|
|
|
double final_m[6];
|
|
tm_multiply(final_m, tm.m, char_m);
|
|
|
|
//printf("final_m = %f,%f,%f,%f,%f,%f\n", final_m[0], final_m[1], final_m[2], final_m[3], final_m[4], final_m[5]);
|
|
double final_after_ctm[6];
|
|
tm_multiply(final_after_ctm, ctm_stack.back(), final_m);
|
|
//printf("final_after_ctm= %f,%f,%f,%f,%f,%f\n", final_after_ctm[0], final_after_ctm[1], final_after_ctm[2], final_after_ctm[3], final_after_ctm[4], final_after_ctm[5]);
|
|
double inset = 0.1;
|
|
double bbox[4] {inset*width, inset*height, (1-inset)*width, (1-inset)*height};
|
|
|
|
//printf("bbox before: [%f,%f,%f,%f]\n", bbox[0],bbox[1],bbox[2],bbox[3]);
|
|
//printf("bbox after: [%f,%f,%f,%f]\n", bbox[0],bbox[1],bbox[2],bbox[3]);
|
|
tm_transform_bbox(final_after_ctm, bbox);
|
|
//printf("bbox after: [%f,%f,%f,%f]\n", bbox[0],bbox[1],bbox[2],bbox[3]);
|
|
draw_char_bbox(state, bbox, inTransparencyGroup);
|
|
}
|
|
|
|
void DrawingTracer::xform_pt(double & x, double & y) {
|
|
tm_transform(ctm_stack.back(), x, y);
|
|
}
|
|
|
|
} /* namespace pdf2htmlEX */
|