2012-09-04 15:33:15 +00:00
|
|
|
/*
|
|
|
|
* LineBuffer.cc
|
|
|
|
*
|
|
|
|
* Generate and optimized HTML for one line
|
|
|
|
*
|
|
|
|
* by WangLu
|
|
|
|
* 2012.09.04
|
|
|
|
*/
|
|
|
|
|
2012-09-05 07:13:21 +00:00
|
|
|
#include <vector>
|
|
|
|
|
2012-09-04 15:33:15 +00:00
|
|
|
#include "HTMLRenderer.h"
|
|
|
|
#include "HTMLRenderer/namespace.h"
|
|
|
|
|
|
|
|
using std::min;
|
|
|
|
using std::max;
|
2012-09-05 07:13:21 +00:00
|
|
|
using std::vector;
|
|
|
|
using std::function;
|
2012-09-04 15:33:15 +00:00
|
|
|
|
|
|
|
void HTMLRenderer::LineBuffer::reset(GfxState * state)
|
|
|
|
{
|
|
|
|
state->transform(state->getCurX(), state->getCurY(), &x, &y);
|
|
|
|
tm_id = renderer->cur_tm_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
void HTMLRenderer::LineBuffer::append_unicodes(const Unicode * u, int l)
|
|
|
|
{
|
|
|
|
text.insert(text.end(), u, u+l);
|
|
|
|
}
|
|
|
|
|
|
|
|
void HTMLRenderer::LineBuffer::append_offset(double width)
|
|
|
|
{
|
|
|
|
if((!offsets.empty()) && (offsets.back().start_idx == text.size()))
|
|
|
|
offsets.back().width += width;
|
|
|
|
else
|
|
|
|
offsets.push_back({text.size(), width});
|
|
|
|
}
|
|
|
|
|
|
|
|
void HTMLRenderer::LineBuffer::append_state(void)
|
|
|
|
{
|
|
|
|
if(states.empty() || (states.back().start_idx != text.size()))
|
|
|
|
{
|
|
|
|
states.resize(states.size() + 1);
|
|
|
|
states.back().start_idx = text.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
set_state(states.back());
|
|
|
|
}
|
|
|
|
|
|
|
|
void HTMLRenderer::LineBuffer::flush(void)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Each Line is an independent absolute positioined block
|
|
|
|
* so even we have a few states or offsets, we may omit them
|
|
|
|
*/
|
|
|
|
if(text.empty()) return;
|
|
|
|
|
|
|
|
if(states.empty() || (states[0].start_idx != 0))
|
|
|
|
{
|
|
|
|
cerr << "Warning: text without a style! Must be a bug in pdf2htmlEX" << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-09-05 07:13:21 +00:00
|
|
|
for(auto & s : states)
|
|
|
|
s.hash();
|
|
|
|
|
2012-09-04 15:33:15 +00:00
|
|
|
states.resize(states.size() + 1);
|
|
|
|
states.back().start_idx = text.size();
|
|
|
|
|
|
|
|
offsets.push_back({text.size(), 0});
|
|
|
|
|
|
|
|
double max_ascent = 0;
|
|
|
|
for(const State & s : states)
|
2012-09-05 07:13:21 +00:00
|
|
|
max_ascent = max(max_ascent, s.ascent * s.draw_font_size);
|
2012-09-04 15:33:15 +00:00
|
|
|
|
|
|
|
// TODO: class for height ?
|
|
|
|
ostream & out = renderer->html_fout;
|
|
|
|
out << format("<div style=\"left:%1%px;bottom:%2%px;height:%3%px;\" class=\"l t%|4$x|\">")
|
|
|
|
% x % y
|
|
|
|
% max_ascent
|
|
|
|
% tm_id
|
|
|
|
;
|
|
|
|
|
|
|
|
auto cur_state_iter = states.begin();
|
|
|
|
auto cur_offset_iter = offsets.begin();
|
|
|
|
|
2012-09-05 07:13:21 +00:00
|
|
|
//accumulated horizontal offset;
|
2012-09-04 15:33:15 +00:00
|
|
|
double dx = 0;
|
|
|
|
|
2012-09-06 06:37:09 +00:00
|
|
|
stack.clear();
|
|
|
|
stack.push_back(nullptr);
|
|
|
|
|
|
|
|
// whenever a negative offset appears, we should not pop out that <span>
|
|
|
|
// otherwise the effect of negative margin-left would disappear
|
2012-09-06 11:05:49 +00:00
|
|
|
size_t last_text_pos_with_negative_offset = 0;
|
2012-09-05 07:13:21 +00:00
|
|
|
|
2012-09-04 15:33:15 +00:00
|
|
|
size_t cur_text_idx = 0;
|
|
|
|
while(cur_text_idx < text.size())
|
|
|
|
{
|
|
|
|
if(cur_text_idx >= cur_state_iter->start_idx)
|
|
|
|
{
|
2012-09-06 06:37:09 +00:00
|
|
|
// greedy
|
|
|
|
int best_cost = State::ID_COUNT;
|
|
|
|
|
|
|
|
// we have a nullptr at the beginning, so no need to check for rend
|
|
|
|
for(auto iter = stack.rbegin(); *iter; ++iter)
|
2012-09-05 07:13:21 +00:00
|
|
|
{
|
2012-09-06 06:37:09 +00:00
|
|
|
int cost = cur_state_iter->diff(**iter);
|
|
|
|
if(cost < best_cost)
|
|
|
|
{
|
|
|
|
while(stack.back() != *iter)
|
|
|
|
{
|
|
|
|
stack.back()->end(out);
|
|
|
|
stack.pop_back();
|
|
|
|
}
|
|
|
|
best_cost = cost;
|
|
|
|
|
|
|
|
if(best_cost == 0)
|
|
|
|
break;
|
|
|
|
}
|
2012-09-05 07:13:21 +00:00
|
|
|
|
2012-09-06 06:37:09 +00:00
|
|
|
// cannot go further
|
|
|
|
if((*iter)->start_idx <= last_text_pos_with_negative_offset)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
cur_state_iter->begin(out, stack.back());
|
|
|
|
stack.push_back(&*cur_state_iter);
|
2012-09-04 15:33:15 +00:00
|
|
|
|
|
|
|
++ cur_state_iter;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(cur_text_idx >= cur_offset_iter->start_idx)
|
|
|
|
{
|
|
|
|
double target = cur_offset_iter->width + dx;
|
|
|
|
double w;
|
|
|
|
|
|
|
|
auto wid = renderer->install_whitespace(target, w);
|
|
|
|
|
2012-09-06 06:37:09 +00:00
|
|
|
if(w < 0)
|
|
|
|
last_text_pos_with_negative_offset = cur_text_idx;
|
|
|
|
|
2012-09-05 07:13:21 +00:00
|
|
|
double threshold = cur_state_iter->draw_font_size * (cur_state_iter->ascent - cur_state_iter->descent) * (renderer->param->space_threshold);
|
2012-09-04 15:33:15 +00:00
|
|
|
out << format("<span class=\"_ _%|1$x|\">%2%</span>") % wid % (target > (threshold - EPS) ? " " : "");
|
|
|
|
|
|
|
|
dx = target - w;
|
|
|
|
|
|
|
|
++ cur_offset_iter;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t next_text_idx = min(cur_state_iter->start_idx, cur_offset_iter->start_idx);
|
|
|
|
outputUnicodes(out, text.data() + cur_text_idx, next_text_idx - cur_text_idx);
|
|
|
|
cur_text_idx = next_text_idx;
|
|
|
|
}
|
|
|
|
|
2012-09-05 07:13:21 +00:00
|
|
|
// we have a nullptr in the bottom
|
2012-09-06 06:37:09 +00:00
|
|
|
while(stack.back())
|
2012-09-05 07:13:21 +00:00
|
|
|
{
|
2012-09-06 06:37:09 +00:00
|
|
|
stack.back()->end(out);
|
|
|
|
stack.pop_back();
|
2012-09-05 07:13:21 +00:00
|
|
|
}
|
|
|
|
|
2012-09-04 15:33:15 +00:00
|
|
|
out << "</div>";
|
|
|
|
|
|
|
|
|
|
|
|
states.clear();
|
|
|
|
offsets.clear();
|
|
|
|
text.clear();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void HTMLRenderer::LineBuffer::set_state (State & state)
|
|
|
|
{
|
|
|
|
state.ids[State::FONT_ID] = renderer->cur_font_info->id;
|
|
|
|
state.ids[State::FONT_SIZE_ID] = renderer->cur_fs_id;
|
|
|
|
state.ids[State::COLOR_ID] = renderer->cur_color_id;
|
|
|
|
state.ids[State::LETTER_SPACE_ID] = renderer->cur_ls_id;
|
|
|
|
state.ids[State::WORD_SPACE_ID] = renderer->cur_ws_id;
|
|
|
|
state.ids[State::RISE_ID] = renderer->cur_rise_id;
|
|
|
|
|
2012-09-05 07:13:21 +00:00
|
|
|
const FontInfo * info = renderer->cur_font_info;
|
|
|
|
state.ascent = info->ascent;
|
|
|
|
state.descent = info->descent;
|
|
|
|
state.draw_font_size = renderer->draw_font_size;
|
2012-09-04 15:33:15 +00:00
|
|
|
}
|
|
|
|
|
2012-09-05 07:13:21 +00:00
|
|
|
void HTMLRenderer::LineBuffer::State::begin (ostream & out, const State * prev_state)
|
|
|
|
{
|
|
|
|
bool first = true;
|
2012-09-04 15:33:15 +00:00
|
|
|
for(int i = 0; i < ID_COUNT; ++i)
|
|
|
|
{
|
2012-09-05 07:13:21 +00:00
|
|
|
if(prev_state && (prev_state->ids[i] == ids[i]))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if(first)
|
|
|
|
{
|
2012-09-06 06:37:09 +00:00
|
|
|
out << "<span class=\"";
|
2012-09-05 07:13:21 +00:00
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
out << ' ';
|
|
|
|
}
|
|
|
|
|
2012-09-04 15:33:15 +00:00
|
|
|
out << format("%1%%|2$x|") % format_str[i] % ids[i];
|
|
|
|
}
|
2012-09-05 07:13:21 +00:00
|
|
|
|
2012-09-06 06:37:09 +00:00
|
|
|
if(first)
|
|
|
|
{
|
|
|
|
need_close = false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
out << "\">";
|
|
|
|
need_close = true;
|
|
|
|
}
|
2012-09-04 15:33:15 +00:00
|
|
|
}
|
|
|
|
|
2012-09-05 07:13:21 +00:00
|
|
|
void HTMLRenderer::LineBuffer::State::end(ostream & out) const
|
|
|
|
{
|
|
|
|
if(need_close)
|
|
|
|
out << "</span>";
|
|
|
|
}
|
|
|
|
|
|
|
|
void HTMLRenderer::LineBuffer::State::hash(void)
|
|
|
|
{
|
|
|
|
hash_value = 0;
|
|
|
|
for(int i = 0; i < ID_COUNT; ++i)
|
|
|
|
{
|
|
|
|
hash_value = (hash_value << 8) | (ids[i] & 0xff);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int HTMLRenderer::LineBuffer::State::diff(const State & s) const
|
2012-09-04 15:33:15 +00:00
|
|
|
{
|
2012-09-05 07:13:21 +00:00
|
|
|
/*
|
|
|
|
* A quick check based on hash_value
|
|
|
|
* it could be wrong when there are more then 256 classes,
|
2012-09-05 08:19:01 +00:00
|
|
|
* in which case the output may not be optimal, but still 'correct' in terms of HTML
|
2012-09-05 07:13:21 +00:00
|
|
|
*/
|
|
|
|
if(hash_value == s.hash_value) return 0;
|
|
|
|
|
|
|
|
int d = 0;
|
|
|
|
for(int i = 0; i < ID_COUNT; ++i)
|
|
|
|
if(ids[i] != s.ids[i])
|
|
|
|
++ d;
|
|
|
|
return d;
|
2012-09-04 15:33:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const char * HTMLRenderer::LineBuffer::State::format_str = "fsclwr";
|